Role-based Authorization

লেসন ৩১ / ৫০

Role-based Authorization

[Authorize(Roles = "Admin")]
public IActionResult AdminOnly() => Ok();

[Authorize(Roles = "Admin,Manager")]      // OR
public IActionResult AdminOrManager() => Ok();

// Multiple [Authorize] = AND
[Authorize(Roles = "Admin")]
[Authorize(Roles = "VerifiedUser")]
public IActionResult Both() => Ok();

Adding Roles to JWT

var claims = new List<Claim> {
    new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
    new(ClaimTypes.Role, user.Role),                 // Admin/User/Manager
    new(ClaimTypes.Role, "VerifiedUser")             // multiple roles
};

Policy-based Authorization ⭐

// Program.cs
builder.Services.AddAuthorization(opt => {
    opt.AddPolicy("AdminOnly", p => p.RequireRole("Admin"));
    opt.AddPolicy("Adult", p => p.RequireClaim("Age", "18", "19", "20" /* ... */));
    opt.AddPolicy("Bangladeshi", p => p.RequireClaim("Country", "BD"));
    opt.AddPolicy("AdminOrOwner", p =>
        p.RequireAssertion(ctx =>
            ctx.User.IsInRole("Admin") ||
            ctx.User.HasClaim("permission", "owner")));
});

// Use
[Authorize(Policy = "AdminOnly")]
public IActionResult Admin() => Ok();

Custom Authorization Requirement

// 1. Requirement
public class MinimumAgeRequirement : IAuthorizationRequirement {
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int age) => MinimumAge = age;
}

// 2. Handler
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> {
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext ctx,
        MinimumAgeRequirement req)
    {
        var dob = ctx.User.FindFirst("DateOfBirth")?.Value;
        if (dob != null) {
            var age = DateTime.UtcNow.Year - DateTime.Parse(dob).Year;
            if (age >= req.MinimumAge) ctx.Succeed(req);
        }
        return Task.CompletedTask;
    }
}

// 3. Register
builder.Services.AddAuthorization(opt => {
    opt.AddPolicy("Adult", p => p.Requirements.Add(new MinimumAgeRequirement(18)));
});
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

Resource-based Authorization ⭐

"User শুধু নিজের post edit করতে পারবে"

public class PostEditHandler : AuthorizationHandler<OperationAuthorizationRequirement, Post> {
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext ctx,
        OperationAuthorizationRequirement req,
        Post post)
    {
        var userId = ctx.User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value;
        if (post.AuthorId.ToString() == userId || ctx.User.IsInRole("Admin"))
            ctx.Succeed(req);
        return Task.CompletedTask;
    }
}

// Controller
[Authorize]
public class PostsController : ControllerBase {
    private readonly IAuthorizationService _auth;

    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, PostDto dto) {
        var post = await _db.Posts.FindAsync(id);
        if (post == null) return NotFound();

        var result = await _auth.AuthorizeAsync(User, post, Operations.Edit);
        if (!result.Succeeded) return Forbid();

        // ... update
        return Ok();
    }
}

Reading Claims in Action

public IActionResult GetMe() {
    int userId = int.Parse(User.FindFirst(JwtRegisteredClaimNames.Sub)!.Value);
    string email = User.FindFirst(ClaimTypes.Email)!.Value;
    bool isAdmin = User.IsInRole("Admin");
    var allRoles = User.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList();

    return Ok(new { userId, email, isAdmin, allRoles });
}