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
Post a Comment