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! 🚀


Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay