RBAC + Refresh Token + Permission Authorization (ULTIMATE)

RBAC + Refresh Token + Permission Authorization (ULTIMATE)

You will get:

  • ✅ JWT Access Token
  • ✅ Refresh Token stored in DB
  • ✅ Refresh Token Rotation (VERY important)
  • ✅ Revoke tokens on logout
  • ✅ Roles + Permissions in DB
  • ✅ [Authorize(Policy="PERMISSION")]
  • ✅ Custom AuthorizationHandler
  • ✅ Clean architecture style (simple but enterprise)

🏗️ 1) Database Tables (Enterprise)

Tables you must have:

  • Users
  • Roles
  • Permissions
  • UserRoles
  • RolePermissions
  • RefreshTokens

✅ 2) Entities (EF Core)

📌 User.cs


public class User
{
    public int Id { get; set; }
    public string FullName { get; set; } = "";
    public string Email { get; set; } = "";
    public string PasswordHash { get; set; } = "";

    public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
    public ICollection<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
}

📌 Role.cs


public class Role
{
    public int Id { get; set; }
    public string Name { get; set; } = "";

    public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
    public ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
}

📌 Permission.cs


public class Permission
{
    public int Id { get; set; }
    public string Code { get; set; } = ""; // USERS_VIEW, USERS_CREATE etc.
}

📌 UserRole.cs


public class UserRole
{
    public int UserId { get; set; }
    public User User { get; set; } = null!;

    public int RoleId { get; set; }
    public Role Role { get; set; } = null!;
}

📌 RolePermission.cs


public class RolePermission
{
    public int RoleId { get; set; }
    public Role Role { get; set; } = null!;

    public int PermissionId { get; set; }
    public Permission Permission { get; set; } = null!;
}

✅ 3) RefreshToken Entity (Rotation Support)

📌 RefreshToken.cs


public class RefreshToken
{
    public int Id { get; set; }

    public int UserId { get; set; }
    public User User { get; set; } = null!;

    public string Token { get; set; } = "";
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public DateTime ExpiresAt { get; set; }

    public bool IsRevoked { get; set; }
    public DateTime? RevokedAt { get; set; }

    // Rotation security
    public string? ReplacedByToken { get; set; }

    public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
    public bool IsActive => !IsRevoked && !IsExpired;
}

✅ 4) DbContext Setup

📌 AppDbContext.cs


using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

    public DbSet<User> Users => Set<User>();
    public DbSet<Role> Roles => Set<Role>();
    public DbSet<Permission> Permissions => Set<Permission>();
    public DbSet<UserRole> UserRoles => Set<UserRole>();
    public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
    public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserRole>().HasKey(x => new { x.UserId, x.RoleId });
        modelBuilder.Entity<RolePermission>().HasKey(x => new { x.RoleId, x.PermissionId });

        base.OnModelCreating(modelBuilder);
    }
}

✅ 5) JWT Token Service (Access Token)

📌 JwtService.cs


using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

public class JwtService
{
    private readonly IConfiguration _config;

    public JwtService(IConfiguration config)
    {
        _config = config;
    }

    public string GenerateAccessToken(User user, List<string> roles, List<string> permissions)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.FullName),
            new Claim(ClaimTypes.Email, user.Email),
        };

        foreach (var role in roles)
            claims.Add(new Claim(ClaimTypes.Role, role));

        foreach (var perm in permissions.Distinct())
            claims.Add(new Claim("permission", perm));

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(15),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

✅ 6) Refresh Token Generator

📌 RefreshTokenGenerator.cs


using System.Security.Cryptography;

public static class RefreshTokenGenerator
{
    public static string Generate()
    {
        var bytes = new byte[64];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

✅ 7) DTOs


// LoginRequest.cs
public record LoginRequest(string Email, string Password);

// LoginResponse.cs
public record LoginResponse(
    string AccessToken,
    string RefreshToken,
    object User
);

// RefreshTokenRequest.cs
public record RefreshTokenRequest(string RefreshToken);

// RefreshTokenResponse.cs
public record RefreshTokenResponse(string AccessToken, string RefreshToken);

✅ 8) AuthController (Login + Refresh + Logout)

📌 AuthController.cs


using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly AppDbContext _db;
    private readonly JwtService _jwt;

    public AuthController(AppDbContext db, JwtService jwt)
    {
        _db = db;
        _jwt = jwt;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login(LoginRequest req)
    {
        var user = await _db.Users
            .Include(x => x.UserRoles)
                .ThenInclude(ur => ur.Role)
                    .ThenInclude(r => r.RolePermissions)
                        .ThenInclude(rp => rp.Permission)
            .FirstOrDefaultAsync(x => x.Email == req.Email);

        if (user == null)
            return Unauthorized("Invalid credentials");

        if (user.PasswordHash != req.Password)
            return Unauthorized("Invalid credentials");

        var roles = user.UserRoles.Select(x => x.Role.Name).ToList();

        var permissions = user.UserRoles
            .SelectMany(x => x.Role.RolePermissions.Select(rp => rp.Permission.Code))
            .Distinct()
            .ToList();

        var accessToken = _jwt.GenerateAccessToken(user, roles, permissions);

        var refreshToken = new RefreshToken
        {
            UserId = user.Id,
            Token = RefreshTokenGenerator.Generate(),
            ExpiresAt = DateTime.UtcNow.AddDays(7)
        };

        _db.RefreshTokens.Add(refreshToken);
        await _db.SaveChangesAsync();

        var userObj = new
        {
            userId = user.Id,
            name = user.FullName,
            roles,
            permissions
        };

        return Ok(new LoginResponse(accessToken, refreshToken.Token, userObj));
    }

    [HttpPost("refresh-token")]
    public async Task<IActionResult> RefreshToken(RefreshTokenRequest req)
    {
        var existing = await _db.RefreshTokens
            .Include(x => x.User)
                .ThenInclude(u => u.UserRoles)
                    .ThenInclude(ur => ur.Role)
                        .ThenInclude(r => r.RolePermissions)
                            .ThenInclude(rp => rp.Permission)
            .FirstOrDefaultAsync(x => x.Token == req.RefreshToken);

        if (existing == null || !existing.IsActive)
            return Unauthorized("Invalid refresh token");

        existing.IsRevoked = true;
        existing.RevokedAt = DateTime.UtcNow;

        var user = existing.User;

        var roles = user.UserRoles.Select(x => x.Role.Name).ToList();

        var permissions = user.UserRoles
            .SelectMany(x => x.Role.RolePermissions.Select(rp => rp.Permission.Code))
            .Distinct()
            .ToList();

        var newAccessToken = _jwt.GenerateAccessToken(user, roles, permissions);

        var newRefreshToken = new RefreshToken
        {
            UserId = user.Id,
            Token = RefreshTokenGenerator.Generate(),
            ExpiresAt = DateTime.UtcNow.AddDays(7)
        };

        existing.ReplacedByToken = newRefreshToken.Token;

        _db.RefreshTokens.Add(newRefreshToken);
        await _db.SaveChangesAsync();

        return Ok(new RefreshTokenResponse(newAccessToken, newRefreshToken.Token));
    }

    [HttpPost("logout")]
    public async Task<IActionResult> Logout(RefreshTokenRequest req)
    {
        var token = await _db.RefreshTokens.FirstOrDefaultAsync(x => x.Token == req.RefreshToken);

        if (token != null)
        {
            token.IsRevoked = true;
            token.RevokedAt = DateTime.UtcNow;
            await _db.SaveChangesAsync();
        }

        return Ok("Logged out");
    }
}

✅ 9) Permission Based Authorization

We create:

  • ✅ [Authorize(Policy = "Permission")]
  • ✅ Custom requirement
  • ✅ Custom handler

9.1 PermissionRequirement.cs


using Microsoft.AspNetCore.Authorization;

public class PermissionRequirement : IAuthorizationRequirement
{
    public string Permission { get; }

    public PermissionRequirement(string permission)
    {
        Permission = permission;
    }
}

9.2 PermissionHandler.cs


using Microsoft.AspNetCore.Authorization;

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        PermissionRequirement requirement)
    {
        var permissions = context.User.Claims
            .Where(c => c.Type == "permission")
            .Select(c => c.Value);

        if (permissions.Contains(requirement.Permission))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

9.3 PermissionPolicyProvider.cs


using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

public class PermissionPolicyProvider : DefaultAuthorizationPolicyProvider
{
    public PermissionPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) {}

    public override Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        if (policyName.StartsWith("PERMISSION:"))
        {
            var permission = policyName.Replace("PERMISSION:", "");

            var policy = new AuthorizationPolicyBuilder()
                .AddRequirements(new PermissionRequirement(permission))
                .Build();

            return Task.FromResult<AuthorizationPolicy?>(policy);
        }

        return base.GetPolicyAsync(policyName);
    }
}

✅ 10) Program.cs Configuration

📌 Program.cs


using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(opt =>
{
    opt.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
});

builder.Services.AddScoped<JwtService>();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opt =>
    {
        opt.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)
            )
        };
    });

builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
builder.Services.AddScoped<IAuthorizationHandler, PermissionHandler>();
builder.Services.AddAuthorization();

builder.Services.AddCors(opt =>
{
    opt.AddPolicy("AllowAngular", policy =>
    {
        policy.AllowAnyHeader()
              .AllowAnyMethod()
              .AllowAnyOrigin();
    });
});

var app = builder.Build();

app.UseCors("AllowAngular");
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

✅ 11) Protect API Endpoints Using Permission

📌 UsersController.cs


using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "PERMISSION:USERS_VIEW")]
    public IActionResult GetUsers()
    {
        return Ok(new[] { "Sai", "Balaji", "AdminUser" });
    }

    [HttpPost]
    [Authorize(Policy = "PERMISSION:USERS_CREATE")]
    public IActionResult CreateUser()
    {
        return Ok("User created");
    }
}

🔥 12) Enterprise Security Must-Haves (INTERVIEW)

  • ✅ Refresh Token Rotation: Every refresh generates new refresh token; Old one revoked; Prevents stolen refresh token reuse
  • ✅ Token Revocation: Logout revokes refresh token
  • ✅ Permission claim in JWT: Very fast permission checks

✅ Final Output (Angular Will Work Perfectly)

Your Angular Tasks B–F will work 100% with this backend.

⭐ Interview Final Statement (Ultimate)

I implemented enterprise authentication using JWT + refresh token rotation, RBAC with roles & permissions stored in DB, and dynamic authorization policies in ASP.NET Core using a custom authorization handler.

Comments

Popular posts from this blog

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Promises in Angular

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features