###### tags: `Back-end`,`Restful API`,`ASP.NET CORE`,`Titanium`,`Security` # Week 08/09 - Authentication & Authorization ## Introduction This session will cover the essentials of JWT (JSON Web Tokens) for authentication in a .NET Core 8 RESTful API, Will use EF Identity package, diving into how JWT works, its structure, best practices, and how to implement it with middleware and role-based route protection. Authentication is the process of verifying who a user is. In a RESTful API, protecting endpoints ensures only authorized users can access certain data or perform specific actions. ## Identity EF Package and Its Role ASP.NET Core Identity provides the tools to handle authentication and authorization. The Identity framework, when combined with Entity Framework Core (EF Core), offers built-in database support for managing user data. Supports secure password storage, user management, roles, and claims-based authentication. ## Installing and Setting Up ASP.NET Core Identity ### Install the required NuGet packages: `dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore ` ### Configure services in Program.cs: ```csharp! builder.Services.AddDbContext<MainAppContext>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddIdentity<User, IdentityRole<int>>() .AddEntityFrameworkStores<MainAppContext>() .AddDefaultTokenProviders(); ``` ### Extending IdentityUser with Integer Primary Key ```csharp! public class ApplicationUser : IdentityUser<int> { // Additional custom properties can go here } ``` ### Update ApplicationDbContext to use IdentityDbContext: ```csharp! public class MainAppContext : IdentityDbContext<ApplicationUser, IdentityRole<int>, int> { public MainAppContext(DbContextOptions<MainAppContext> options) : base(options) { } } ``` ### Database Migration and Update Use the following commands to apply migrations and update the database: ``` dotnet ef migrations add IdentityMig dotnet ef database update OR Add-Migration IdentityMig Update-Database ``` ## Implementing an Authentication Controller (AuthController) The AuthController will include a Login endpoint to authenticate users by checking their credentials. ```csharp! [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly UserManager<ApplicationUser> _userManager; private readonly JwtService _jwtService; public AuthController(UserManager<ApplicationUser> userManager, JwtService jwtService) { _userManager = userManager; _jwtService = jwtService; } [HttpPost("login")] public async Task<IActionResult> Login([FromBody] AuthRequest model) { var user = await _userManager.FindByNameAsync(model.Username); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { var role = (await _userManager.GetRolesAsync(user)).FirstOrDefault(); var token = _jwtService.GenerateJWT(user, role); return Ok(new { Token = token }); } return Unauthorized(); } } ``` DTO for Login: ```csharp! public class AuthRequest { public string Username { get; set; } public string Password { get; set; } } ``` ## JWT JWT is a secure, compact token format that’s commonly used to manage authentication between a client and server. Once a user logs in, the server generates a JWT that’s sent to the client. This token, which is self-contained and includes claims about the user (like roles and permissions), enables the client to make authenticated requests without re-authenticating. ### JWT Structure A JWT has three main parts: * Header: Defines the token type and the signing algorithm (e.g., HS256). * Payload: Contains claims (statements about the user, such as username, roles, exp for expiry, etc.). * Signature: Ensures the token's integrity by signing the encoded header and payload with a secret key. The format of a JWT is: `{Header}.{Payload}.{Signature}` ### Install JwtBearer for JWT implementation Install JwtBearer package from Nuget Manager. ### Setup JWT In ConfigureServices, add authentication and configure JWT bearer settings. Make sure to inject UserManager and SignInManager for user authentication. ```csharp public void ConfigureServices(IServiceCollection services) { // Add Identity and configure it services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // JWT Authentication configuration var jwtSettings = Configuration.GetSection("Jwt"); var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]); 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 = jwtSettings["Issuer"], ValidAudience = jwtSettings["Audience"], IssuerSigningKey = new SymmetricSecurityKey(key) }; }); services.AddControllers(); } ``` Jwt options setup: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "yourapi.com", ValidAudience = "yourapi.com", IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("YourSecretKey")) }; }); ``` ### Implementing JwtService ```csharp public class JwtService { private readonly IConfiguration _config; public JwtService(IConfiguration config) { _config = config; } public string GenerateJWT(ApplicationUser user, string 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: new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(ClaimTypes.Role, role) }, expires: DateTime.Now.AddMinutes(Convert.ToDouble(_config["Jwt:ExpireInMinutes"])), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } ``` Register JwtService in Program.cs: ```jsonld "Jwt": { "Key": "asdkjkejr##$@daslkw[efkf]fadirai", "Issuer": "Titanium", "Audience": "Trainers.Trainees", "ExpireInMinutes": 30 } ``` ### Testing the Login Endpoint Use Postman or a similar tool to send a POST request to /api/auth/login with the correct username and password. If successful, a JWT token will be returned in the response. ### Enable Jwt Middleware to protect the endpoints Middleware for extracting and validating Jwt Token ```csharp! public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); // Add Authentication and Authorization middleware app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } ``` ### Using [Authorize] Attribute to Protect Endpoints Apply [Authorize] on controllers or actions to restrict access to authenticated users: ```csharp [Authorize] [ApiController] [Route("api/[controller]")] public class ProgramsController : ControllerBase { // Only accessible to authenticated users } ``` Checking controller: ```csharp= [Authorize] [Route("api/[controller]")] [ApiController] public class SampleController : ControllerBase { [HttpGet] public IActionResult GetSecureData() { return Ok("This is a secure endpoint"); } } ``` You can specify roles if you need role-based authorization: ```csharp! [Authorize(Roles = "Admin")] public class AdminController : ControllerBase { [HttpGet] public IActionResult GetAdminData() { return Ok("This is an Admin-only endpoint"); } } ``` ## Implementing Role-Based Access Control ```csharp [ApiController] [Route("api/[controller]")] public class RolesController : ControllerBase { private readonly RoleManager<ApplicationRole> _roleManager; public RolesController(RoleManager<ApplicationRole> roleManager) { _roleManager = roleManager; } [HttpPost("create")] public async Task<IActionResult> CreateRole([FromBody] string roleName) { var result = await _roleManager.CreateAsync(new ApplicationRole { Name = roleName }); return result.Succeeded ? Ok() : BadRequest(result.Errors); } [HttpGet("getAll")] public IActionResult GetAllRoles() { var roles = _roleManager.Roles.ToList(); return Ok(roles); } } ``` UsersController to Assign Roles: ```csharp [ApiController] [Route("api/[controller]")] public class UsersController : ControllerBase { private readonly UserManager<ApplicationUser> _userManager; public UsersController(UserManager<ApplicationUser> userManager) { _userManager = userManager; } [HttpPost("assignRole")] public async Task<IActionResult> AssignRole(string userName, string roleName) { var user = await _userManager.FindByNameAsync(userName); if (user == null) return NotFound(); var result = await _userManager.AddToRoleAsync(user, roleName); return result.Succeeded ? Ok() : BadRequest(result.Errors); } } ```