DEV Community

Sann Lynn Htun
Sann Lynn Htun

Posted on

Building a Custom Role-Based Authorization System in ASP.NET Core

In this guide, we’ll walk through the implementation of a custom role-based authorization system in ASP.NET Core. This system includes user registration, login, role management, and secure endpoint access using custom authorization attributes. We’ll also cover how to set up the project using EF Core Database-First, install the necessary NuGet packages, and configure Swagger for testing.


Step-by-Step: Package Installation and EF Core Database-First Setup

1. Install Required NuGet Packages

Open your project in Visual Studio or your preferred IDE, and install the following NuGet packages:

Core Packages

  • Microsoft.EntityFrameworkCore: The main EF Core package.
  • Microsoft.EntityFrameworkCore.SqlServer: For SQL Server database support.
  • Microsoft.EntityFrameworkCore.Tools: For EF Core migrations and scaffolding.
  • Microsoft.EntityFrameworkCore.Design: Required for EF Core design-time tools (e.g., Scaffold-DbContext).

Run the following commands in the Package Manager Console:

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.Design
Enter fullscreen mode Exit fullscreen mode

Additional Packages

  • Effortless.Net.Encryption: For password hashing and token encryption.
  • Newtonsoft.Json: For JSON serialization and deserialization.
  • Swashbuckle.AspNetCore: For Swagger API documentation.

Run the following commands:

Install-Package Effortless.Net.Encryption
Install-Package Newtonsoft.Json
Install-Package Swashbuckle.AspNetCore
Enter fullscreen mode Exit fullscreen mode

2. Set Up the Database

Create the database using the following SQL scripts:

Tbl_User

Stores user information, including username, hashed password, and email.

CREATE TABLE Tbl_User (
    UserId INT PRIMARY KEY IDENTITY(1,1),
    Username NVARCHAR(100) NOT NULL,
    Password NVARCHAR(256) NOT NULL, -- Hashed password
    Email NVARCHAR(100) NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Tbl_Role

Stores roles and their descriptions.

CREATE TABLE Tbl_Role (
    RoleId INT PRIMARY KEY IDENTITY(1,1),
    RoleName NVARCHAR(50) NOT NULL,
    RoleDescription NVARCHAR(255)
);
Enter fullscreen mode Exit fullscreen mode

Tbl_RolePermission

Links users to their roles.

CREATE TABLE Tbl_RolePermission (
    RolePermissionId INT PRIMARY KEY IDENTITY(1,1),
    RoleId INT NOT NULL,
    UserId INT NOT NULL,
    FOREIGN KEY (RoleId) REFERENCES Tbl_Role(RoleId),
    FOREIGN KEY (UserId) REFERENCES Tbl_User(UserId)
);
Enter fullscreen mode Exit fullscreen mode

Tbl_UserLogin

Tracks user login sessions.

CREATE TABLE Tbl_UserLogin (
    UserLoginId INT PRIMARY KEY IDENTITY(1,1),
    UserId INT NOT NULL,
    SessionId NVARCHAR(50) NOT NULL,
    SessionExpiredDate DATETIME NOT NULL,
    LogoutDate DATETIME NULL,
    FOREIGN KEY (UserId) REFERENCES Tbl_User(UserId)
);
Enter fullscreen mode Exit fullscreen mode

3. Scaffold the Database Using EF Core

Use the Scaffold-DbContext command to generate the entity classes and DbContext from your database. Replace the placeholders with your database connection details:

Scaffold-DbContext "Server=YOUR_SERVER_NAME;Database=YOUR_DATABASE_NAME;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Database/Models -ContextDir Database/Data -Context ApplicationDbContext
Enter fullscreen mode Exit fullscreen mode

Verify the Generated Files

After running the command, you should see the following files in your project:

  • Entity Classes: Generated in the Database/Models folder (e.g., TblUser.cs, TblRole.cs).
  • DbContext Class: Generated in the Database/Data folder (e.g., ApplicationDbContext.cs).

4. Configure the DbContext in Program.cs

Register the ApplicationDbContext in the dependency injection container. Open Program.cs and add the following code:

var builder = WebApplication.CreateBuilder(args);

// Add DbContext with SQL Server
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();
Enter fullscreen mode Exit fullscreen mode

Add Connection String to appsettings.json

Add your database connection string to the appsettings.json file:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=YOUR_SERVER_NAME;Database=YOUR_DATABASE_NAME;Trusted_Connection=True;"
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom Role-Based Authorization System

1. Authentication and User Services

AuthService

Handles user login and token generation.

public class AuthService
{
    private readonly ApplicationDbContext _db;

    public AuthService(ApplicationDbContext db)
    {
        _db = db;
    }

    public string Login(string username, string password)
    {
        var user = _db.TblUsers.FirstOrDefault(u => u.Username == username);

        if (user == null || !Hash.Verify(HashType.SHA256, password, "YourSalt", false, user.Password))
        {
            throw new UnauthorizedAccessException("Invalid credentials");
        }

        string sessionId = Ulid.NewUlid().ToString();
        var roles = _db.TblRolePermissions
            .Where(rp => rp.UserId == user.UserId)
            .Select(rp => rp.Role.RoleName)
            .ToList();

        _db.TblUserLogins.Add(new TblUserLogin
        {
            UserId = user.UserId,
            SessionId = sessionId,
            SessionExpiredDate = DateTime.UtcNow.AddHours(1), // 1-hour session
            LogoutDate = null
        });

        _db.SaveChanges();

        return TokenService.GenerateToken(user.UserId, sessionId, roles);
    }
}
Enter fullscreen mode Exit fullscreen mode

UserService

Handles user registration and role assignment.

public class UserService
{
    private readonly ApplicationDbContext _db;

    public UserService(ApplicationDbContext db)
    {
        _db = db;
    }

    public void RegisterUser(string username, string password, string email, List<string> roles)
    {
        string hashedPassword = Hash.Create(HashType.SHA256, password, "YourSalt", false);

        var user = new TblUser
        {
            Username = username,
            Password = hashedPassword,
            Email = email
        };

        _db.TblUsers.Add(user);
        _db.SaveChanges();

        foreach (var roleName in roles)
        {
            var role = _db.TblRoles.FirstOrDefault(r => r.RoleName == roleName);
            if (role != null)
            {
                _db.TblRolePermissions.Add(new TblRolePermission
                {
                    RoleId = role.RoleId,
                    UserId = user.UserId
                });
            }
        }

        _db.SaveChanges();
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Token Service

The TokenService class handles token generation and decryption using symmetric encryption.

public static class TokenService
{
    private static readonly string EncryptionKey = "YourSecretEncryptionKey";

    public static string GenerateToken(int userId, string sessionId, List<string> roles)
    {
        var tokenData = new
        {
            UserId = userId,
            SessionId = sessionId,
            Roles = roles
        };

        string jsonToken = JsonConvert.SerializeObject(tokenData);
        byte[] encryptedBytes = AES.Encrypt(jsonToken, EncryptionKey, Bytes.GenerateSalt());
        return Convert.ToBase64String(encryptedBytes);
    }

    public static (int UserId, string SessionId, List<string> Roles) DecryptToken(string encryptedToken)
    {
        byte[] encryptedBytes = Convert.FromBase64String(encryptedToken);
        string jsonToken = AES.Decrypt(encryptedBytes, EncryptionKey, Bytes.GenerateSalt());
        return JsonConvert.DeserializeObject<(int UserId, string SessionId, List<string> Roles)>(jsonToken);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Custom Authorization Attribute

The CustomAuthorizeAttribute enforces role-based access control on endpoints.

public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
    private readonly string[] _requiredRoles;

    public CustomAuthorizeAttribute(params string[] requiredRoles)
    {
        _requiredRoles = requiredRoles;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var token))
        {
            context.Result = new UnauthorizedResult(); // 401 Unauthorized
            return;
        }

        try
        {
            var (userId, sessionId, roles) = TokenService.DecryptToken(token);

            using (var db = new ApplicationDbContext())
            {
                var session = db.TblUserLogins
                    .FirstOrDefault(ul => ul.UserId == userId && ul.SessionId == sessionId && ul.SessionExpiredDate > DateTime.UtcNow && ul.LogoutDate == null);

                if (session == null)
                {
                    context.Result = new UnauthorizedResult(); // 401 Unauthorized
                    return;
                }
            }

            if (_requiredRoles.Length > 0 && !_requiredRoles.Any(role => roles.Contains(role)))
            {
                // Return 403 Forbidden if the user doesn't have the required roles
                context.Result = new StatusCodeResult(403); // 403 Forbidden
                return;
            }

            // Store user information in HttpContext for later use
            context.HttpContext.Items["UserId"] = userId;
            context.HttpContext.Items["Roles"] = roles;
        }
        catch
        {
            context.Result = new UnauthorizedResult(); // 401 Unauthorized
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Secure Endpoints

The SecureController demonstrates how to use the CustomAuthorizeAttribute to protect endpoints.

[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet("admin")]
    [CustomAuthorize("Admin")] // Requires "Admin" role
    public IActionResult AdminEndpoint()
    {
        return Ok("Welcome, Admin!");
    }

    [HttpGet("user")]
    [CustomAuthorize("User")] // Requires "User" role
    public IActionResult UserEndpoint()
    {
        return Ok("Welcome, User!");
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Swagger Configuration

Configure Swagger to include support for the Authorization header.

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "CustomRoleBasedAuthorization", Version = "v1" });

    // Add support for the Authorization header in Swagger
    options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
    {
        Description = "Custom Authorization header using an API key. Example: \"Authorization: <your-key>\"",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "ApiKey"
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "ApiKey"
                }
            },
            Array.Empty<string>()
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

6. Register Services in Program.cs

Register the AuthService and UserService in the dependency injection container.

builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<UserService>();
Enter fullscreen mode Exit fullscreen mode

Now You Can Run, Test, and Enjoy Coding!

With everything set up, you’re ready to run the application, test the endpoints, and see your custom role-based authorization system in action. Use tools like Swagger or Postman to interact with your API and verify that the authentication and authorization logic works as expected.


Explore the Source Code

If you'd like to dive deeper into the implementation, feel free to explore the complete source code on GitHub. You can find the repository here:

🔗 CustomRoleBasedAuthorization on GitHub

Feel free to clone the repository, experiment with the code, and adapt it to your own projects. If you find it helpful, don’t forget to give it a ⭐️ on GitHub!

Happy coding! 🚀


Top comments (0)