ASP.NET Core  

Implementing Role-Based Security in ASP.NET Core + Angular Apps

Introduction

In any enterprise application, security is critical. One of the most common security models is role-based access control (RBAC). RBAC ensures that users can only access resources and perform actions according to their roles, such as Admin, Manager, or Employee.

When building applications with ASP.NET Core as the backend and Angular as the frontend, implementing RBAC involves:

  • Securing APIs with roles

  • Assigning roles to users

  • Restricting UI components in Angular

  • Handling token-based authentication

This guide explains step-by-step how to implement RBAC in a production-ready manner.

1. Understanding Role-Based Access Control

Roles define what a user can or cannot do. RBAC is simpler to maintain than user-based permissions.

Example Roles in a System

RolePermissions
AdminCreate, read, update, delete all resources
ManagerRead and update resources for their team
EmployeeRead-only access to assigned resources

RBAC Principles

  1. Users are assigned one or more roles.

  2. Roles determine permissions.

  3. Roles are validated on both server and client.

2. ASP.NET Core Authentication Setup

2.1 Install Identity

For ASP.NET Core, we can use Identity for user and role management.

dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore

2.2 Configure Identity

In Program.cs:

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.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"]))
    };
});

3. Creating Roles and Admin User

You can seed roles at startup:

public static async Task SeedRolesAndAdmin(IServiceProvider serviceProvider)
{
    var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
    var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();

    string[] roles = { "Admin", "Manager", "Employee" };

    foreach (var role in roles)
    {
        if (!await roleManager.RoleExistsAsync(role))
        {
            await roleManager.CreateAsync(new IdentityRole(role));
        }
    }

    // Create default admin
    var adminEmail = "[email protected]";
    var adminUser = await userManager.FindByEmailAsync(adminEmail);
    if (adminUser == null)
    {
        adminUser = new ApplicationUser { UserName = adminEmail, Email = adminEmail };
        await userManager.CreateAsync(adminUser, "Admin@123");
        await userManager.AddToRoleAsync(adminUser, "Admin");
    }
}

Call this method in Program.cs after building the app:

using (var scope = app.Services.CreateScope())
{
    await SeedRolesAndAdmin(scope.ServiceProvider);
}

4. Protecting APIs With Roles

ASP.NET Core provides the [Authorize] attribute to secure controllers and actions.

[Authorize(Roles = "Admin")]
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAllUsers()
    {
        // Only Admins can access
        return Ok(new { Message = "This is admin-only data." });
    }
}

[Authorize(Roles = "Manager,Admin")]
[HttpPost("update")]
public IActionResult UpdateUser([FromBody] UserDto user)
{
    // Admin and Manager can update users
    return Ok();
}

Key Points

  • [Authorize(Roles = "Admin")] → only users in Admin role can access.

  • [Authorize(Roles = "Manager,Admin")] → multiple roles allowed.

  • Roles are validated on the server, preventing unauthorized access even if someone modifies the Angular UI.

5. Implementing Role-Based Authorization in JWT

For SPAs like Angular, we use JWT (JSON Web Tokens) to carry roles.

5.1 Adding Roles to JWT

private async Task<string> GenerateJwtToken(ApplicationUser user)
{
    var roles = await _userManager.GetRolesAsync(user);

    var claims = new List<Claim>
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
    };

    claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

    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.Now.AddHours(2),
        signingCredentials: creds
    );

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

Now the JWT carries role information that Angular can use for conditional UI rendering.

6. Angular Role-Based Access

6.1 Auth Service

@Injectable({ providedIn: 'root' })
export class AuthService {
  private tokenKey = 'auth_token';

  constructor(private router: Router) {}

  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  getRoles(): string[] {
    const token = this.getToken();
    if (!token) return [];
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] 
           ? [payload['http://schemas.microsoft.com/ws/2008/06/identity/claims/role']] 
           : [];
  }

  isInRole(role: string): boolean {
    return this.getRoles().includes(role);
  }
}

6.2 Role-Based Route Guards

@Injectable({ providedIn: 'root' })
export class RoleGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot): boolean {
    const roles = route.data['roles'] as string[];
    if (!roles || roles.length === 0) return true;

    if (roles.some(role => this.auth.isInRole(role))) return true;

    this.router.navigate(['/unauthorized']);
    return false;
  }
}

6.3 Protecting Angular Routes

const routes: Routes = [
  { path: 'admin', component: AdminComponent, canActivate: [RoleGuard], data: { roles: ['Admin'] } },
  { path: 'manager', component: ManagerComponent, canActivate: [RoleGuard], data: { roles: ['Manager', 'Admin'] } },
  { path: '**', redirectTo: 'login' }
];

6.4 Conditional UI Rendering

<button *ngIf="auth.isInRole('Admin')">Delete User</button>

This ensures that UI elements are only visible to users with the appropriate role. Remember, server-side validation is still critical, as frontend-only checks can be bypassed.

7. Best Practices

  1. Always validate roles on the server. Frontend checks are for UX only.

  2. Use JWT claims for role management in SPAs.

  3. Centralize role constants to avoid typos.

  4. Use multiple roles in [Authorize] attributes for flexible access.

  5. Secure sensitive APIs with [Authorize(Roles = "Admin")] instead of only [Authorize].

  6. Handle token expiration gracefully in Angular.

  7. Seed default roles in the database for initial deployment.

8. Optional Enhancements

  • Hierarchical roles: Admin > Manager > Employee

  • Dynamic roles: Store role-permissions in DB for runtime updates

  • Claims-based access: Use claims for fine-grained control, e.g., CanEditUser

  • Policy-based authorization: ASP.NET Core policies allow complex conditions

Example

services.AddAuthorization(options =>
{
    options.AddPolicy("EditPolicy", policy =>
        policy.RequireRole("Admin", "Manager")
              .RequireClaim("CanEditUser", "true"));
});

Conclusion

Role-Based Security is essential for protecting your application. By combining:

  • ASP.NET Core Identity for user and role management

  • JWT tokens carrying roles

  • [Authorize] attributes for API protection

  • Angular route guards and conditional UI rendering

…you can create a robust, production-ready RBAC system.

Key takeaways

  1. Always validate roles on the server.

  2. Use JWT to propagate roles to Angular.

  3. Combine guards and conditional rendering for better UX.

  4. Seed default roles and users for easier deployment.

  5. Use policy-based authorization for complex business rules.

This approach works well for enterprise dashboards, admin panels, and multi-role applications.