--- marp: true paginate: true math: mathjax theme: buutti title: 7. Authentication and Authorization --- # Authentication and Authorization ## Contents - [Introduction to Authentication & Authorization](#introduction-to-authentication--authorization) - [JWT Tokens](#jwt-tokens) - [Setting up Authentication in ASP.NET Core](#setting-up-authentication-in-aspnet-core) - [User Registration and Login](#user-registration-and-login) - [Protected Endpoints](#protected-endpoints) - [Role-based Authorization](#role-based-authorization) - [Testing Authentication](#testing-authentication) ## Introduction to Authentication & Authorization ### What is authentication? * ***Authentication*** is the process of verifying who a user is * *"Who are you?"* * Confirms the identity of a user trying to access the system * Common authentication methods: * Username/email and password * Social login (Google, Facebook, etc.) * Multi-factor authentication (MFA) * Biometric authentication ### What is authorization? * ***Authorization*** is the process of determining what a user can access * *"What are you allowed to do?"* * Controls access to resources based on user permissions * Example: Only admins can delete users * Different authorization levels: * ***Public***: Anyone can access * ***Authenticated***: Only logged-in users can access * ***Role-based***: Only users with specific roles can access * ***Resource-based***: Users can only access their own resources ### Why do we need authentication & authorization? * ***Security***: Protect sensitive data and operations * ***User experience***: Personalized content and features * ***Compliance***: Meet legal and regulatory requirements * ***Audit Trail***: Track who did what and when * ***Resource management***: Control access to limited resources ## JWT Tokens ### What is JWT? * [JWT](https://auth0.com/docs/secure/tokens/json-web-tokens) (JSON Web Token) is an open standard for a compact, URL-safe means of representing claims between two parties * Self-contained tokens that include all necessary information * It is ***stateless***: no need to store session data on the server * ***Notes:*** * Tokens have a set expiration time, and cannot be invalidated beforehand * Client must store tokens securely! ### JWT structure ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` * JWTs consist of three parts separated by dots `.` * ***Header*** contains token type and signing algorithm * ***Payload*** contains claims (user data, permissions, etc.) * ***Signature*** verifies the token hasn't been tampered with ### JWT claims * There are two types of [JWT claims](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims), ***registered*** (defined in the JWT specification) and ***custom*** (which can be either public or private)
#### Some registered claims (standard) * `iss` (issuer): Who issued the token * `sub` (subject): Who the token is for * `exp` (expiration): When the token expires * `iat` (issued at): When the token was issued
#### Custom claim examples * `userId`: User's unique identifier * `email`: User's email address * `role`: User's role (admin, user, etc.) * `permissions`: Specific permissions
## Setting up Authentication in ASP.NET Core ### Required NuGet Packages * Install the following packages from the NuGet Package manager: ```bash Microsoft.AspNetCore.Authentication.JwtBearer Microsoft.IdentityModel.Tokens System.IdentityModel.Tokens.Jwt ``` ### Configuration in appsettings.json ```json { "JwtSettings": { "SecretKey": "your-super-secret-key-with-at-least-32-characters", "Issuer": "your-app-name", "Audience": "your-app-users", "ExpirationInMinutes": 60 }, "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=AuthDemoDb;Trusted_Connection=true;MultipleActiveResultSets=true" } } ``` * To generate the `SecretKey`, you can use [random.org](https://www.random.org/strings) ### Program.cs Configuration ```csharp using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container builder.Services.AddControllers(); // Configure JWT Authentication var jwtSettings = builder.Configuration.GetSection("JwtSettings"); var secretKey = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]); 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 = jwtSettings["Issuer"], ValidAudience = jwtSettings["Audience"], IssuerSigningKey = new SymmetricSecurityKey(secretKey) }; }); builder.Services.AddAuthorization(); var app = builder.Build(); // Configure the HTTP request pipeline app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); ``` ### Exercise 1: Basic JWT Setup 1) Create a new ASP.NET Core Web API project 2) Install the required NuGet packages 3) Configure JWT authentication in `Program.cs` 4) Add JWT settings to `appsettings.json` 5) Test that the application starts without errors ## User Registration and Login ### User Model ```csharp public class User { public int Id { get; set; } public string Username { get; set; } public string Email { get; set; } public string PasswordHash { get; set; } public string Role { get; set; } = "User"; public DateTime CreatedAt { get; set; } = DateTime.UtcNow; } ``` ### DTOs (Data Transfer Objects) ```csharp public class RegisterRequest { [Required] [StringLength(50)] public string Username { get; set; } [Required] [EmailAddress] public string Email { get; set; } [Required] [StringLength(100, MinimumLength = 6)] public string Password { get; set; } } public class LoginRequest { [Required] public string Username { get; set; } [Required] public string Password { get; set; } } public class AuthResponse { public string Token { get; set; } public string Username { get; set; } public string Role { get; set; } public DateTime ExpiresAt { get; set; } } ``` ### JWT Service ```csharp public interface IJwtService { string GenerateToken(User user); bool ValidateToken(string token); } public class JwtService : IJwtService { private readonly IConfiguration _configuration; public JwtService(IConfiguration configuration) { _configuration = configuration; } public string GenerateToken(User user) { var jwtSettings = _configuration.GetSection("JwtSettings"); var secretKey = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Role, user.Role) }), Expires = DateTime.UtcNow.AddMinutes( Convert.ToDouble(jwtSettings["ExpirationInMinutes"]) ), Issuer = jwtSettings["Issuer"], Audience = jwtSettings["Audience"], SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature ) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } public bool ValidateToken(string token) { var jwtSettings = _configuration.GetSection("JwtSettings"); var secretKey = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]); var tokenHandler = new JwtSecurityTokenHandler(); try { tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(secretKey), ValidateIssuer = true, ValidIssuer = jwtSettings["Issuer"], ValidateAudience = true, ValidAudience = jwtSettings["Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); return true; } catch { return false; } } } ``` ### Auth Controller ```csharp [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IUserService _userService; private readonly IJwtService _jwtService; public AuthController(IUserService userService, IJwtService jwtService) { _userService = userService; _jwtService = jwtService; } [HttpPost("register")] public async Task> Register(RegisterRequest request) { try { var user = await _userService.RegisterUser(request); var token = _jwtService.GenerateToken(user); return Ok(new AuthResponse { Token = token, Username = user.Username, Role = user.Role, ExpiresAt = DateTime.UtcNow.AddMinutes(60) }); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("login")] public async Task> Login(LoginRequest request) { try { var user = await _userService.ValidateUser(request); var token = _jwtService.GenerateToken(user); return Ok(new AuthResponse { Token = token, Username = user.Username, Role = user.Role, ExpiresAt = DateTime.UtcNow.AddMinutes(60) }); } catch (Exception ex) { return Unauthorized(new { message = ex.Message }); } } } ``` ### Exercise 2: User Authentication Implementation 1) Create the `User` model and DTOs 2) Implement the `JwtService` class 3) Create the `AuthController` with register and login endpoints 4) Implement password hashing using `BCrypt.Net.BCrypt` 5) Test the registration and login endpoints using Postman ## Protected Endpoints ### Using [Authorize] Attribute ```csharp [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { [HttpGet("profile")] [Authorize] // Requires authentication public ActionResult GetProfile() { var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var username = User.FindFirst(ClaimTypes.Name)?.Value; var email = User.FindFirst(ClaimTypes.Email)?.Value; return Ok(new UserProfile { Id = int.Parse(userId), Username = username, Email = email }); } [HttpPut("profile")] [Authorize] // Requires authentication public ActionResult UpdateProfile(UpdateProfileRequest request) { // Update user profile logic return Ok(new { message = "Profile updated successfully" }); } } ``` ### Accessing User Claims ```csharp [HttpGet("me")] [Authorize] public ActionResult GetCurrentUser() { var claims = new { UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value, Username = User.FindFirst(ClaimTypes.Name)?.Value, Email = User.FindFirst(ClaimTypes.Email)?.Value, Role = User.FindFirst(ClaimTypes.Role)?.Value, IsAuthenticated = User.Identity.IsAuthenticated }; return Ok(claims); } ``` ### Custom Authorization Attributes ```csharp public class RequireAdminAttribute : AuthorizeAttribute { public RequireAdminAttribute() { Roles = "Admin"; } } [HttpGet("admin-only")] [RequireAdmin] public ActionResult AdminOnly() { return Ok(new { message = "This endpoint is only for admins" }); } ``` ### Exercise 3: Protected Endpoints 1) Create a `UserController` with protected endpoints 2) Implement an endpoint that returns the current user's profile 3) Create a custom authorization attribute for admin users 4) Test the endpoints with and without valid JWT tokens 5) Verify that unauthorized requests return 401 status codes ## Role-based Authorization ### Role-based Access Control (RBAC) * ***Roles*** define sets of permissions * Users are assigned one or more roles * Endpoints are protected based on required roles * Common roles: `Admin`, `User`, `Moderator`, `Guest` ### Using Role-based Authorization ```csharp [ApiController] [Route("api/[controller]")] public class AdminController : ControllerBase { [HttpGet("users")] [Authorize(Roles = "Admin")] public ActionResult> GetAllUsers() { // Only admins can access this endpoint return Ok(/* user list */); } [HttpDelete("users/{id}")] [Authorize(Roles = "Admin")] public ActionResult DeleteUser(int id) { // Only admins can delete users return Ok(new { message = "User deleted successfully" }); } [HttpGet("stats")] [Authorize(Roles = "Admin,Moderator")] public ActionResult GetStats() { // Both admins and moderators can access this return Ok(/* statistics */); } } ``` ### Multiple Roles ```csharp [HttpGet("moderate")] [Authorize(Roles = "Admin,Moderator")] public ActionResult ModerateContent() { // Users with Admin OR Moderator role can access return Ok(new { message = "Content moderated" }); } [HttpGet("super-admin")] [Authorize(Roles = "Admin")] [Authorize(Roles = "SuperAdmin")] public ActionResult SuperAdminOnly() { // Users must have BOTH Admin AND SuperAdmin roles return Ok(new { message = "Super admin access granted" }); } ``` ### Policy-based Authorization ```csharp // In Program.cs builder.Services.AddAuthorization(options => { options.AddPolicy("MinimumAge", policy => policy.RequireAssertion(context => { var ageClaim = context.User.FindFirst("Age"); if (ageClaim != null && int.TryParse(ageClaim.Value, out int age)) { return age >= 18; } return false; })); options.AddPolicy("CanEditProfile", policy => policy.RequireAssertion(context => { var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var requestedUserId = context.Resource as string; return userId == requestedUserId || context.User.IsInRole("Admin"); })); }); // In controller [HttpPut("profile/{userId}")] [Authorize(Policy = "CanEditProfile")] public ActionResult UpdateProfile(string userId, UpdateProfileRequest request) { // Users can only edit their own profile, unless they're admin return Ok(new { message = "Profile updated" }); } ``` ### Exercise 4: Role-based Authorization 1) Create an `AdminController` with role-protected endpoints 2) Implement endpoints that require different roles 3) Create a policy that allows users to edit only their own profiles 4) Test role-based access with different user roles 5) Verify that users without required roles get 403 Forbidden responses ## Testing Authentication ### Testing with Postman
* Register a new user: ``` POST /api/auth/register Content-Type: application/json { "username": "testuser", "email": "test@example.com", "password": "password123" } ```
* Login to get JWT token: ``` POST /api/auth/login Content-Type: application/json { "username": "testuser", "password": "password123" } ``` * Use JWT token in protected requests: ``` GET /api/user/profile Authorization: Bearer ```
### JWT Token Structure Analysis * Decode JWT tokens at [jwt.io](https://jwt.io) * Examine the payload to see user claims * Verify token expiration and issuer information ### Testing Different Scenarios * **Valid token**: Should return 200 OK * **Invalid token**: Should return 401 Unauthorized * **Expired token**: Should return 401 Unauthorized * **Missing token**: Should return 401 Unauthorized * **Wrong role**: Should return 403 Forbidden ### Exercise 5: Testing Authentication 1) Create a Postman collection for testing authentication 2) Test user registration with valid and invalid data 3) Test login with correct and incorrect credentials 4) Test protected endpoints with valid JWT tokens 5) Test role-based endpoints with different user roles 6) Verify error responses for unauthorized access ## Security Best Practices ### Password Security * Use strong password requirements * Hash passwords with bcrypt or similar * Never store plain-text passwords * Implement password reset functionality ```csharp // Password hashing public string HashPassword(string password) { return BCrypt.Net.BCrypt.HashPassword(password); } public bool VerifyPassword(string password, string hash) { return BCrypt.Net.BCrypt.Verify(password, hash); } ``` ### JWT Security * Use strong secret keys (at least 32 characters) * Set appropriate token expiration times * Use HTTPS in production * Implement token refresh mechanism * Consider token blacklisting for logout ### Input Validation ```csharp public class RegisterRequest { [Required] [StringLength(50, MinimumLength = 3)] [RegularExpression(@"^[a-zA-Z0-9_]+$")] public string Username { get; set; } [Required] [EmailAddress] public string Email { get; set; } [Required] [StringLength(100, MinimumLength = 8)] [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]")] public string Password { get; set; } } ``` ### Rate Limiting ```csharp // In Program.cs builder.Services.AddRateLimiter(options => { options.GlobalLimiter = PartitionedRateLimiter.Create(context => RateLimitPartition.GetFixedWindowLimiter( partitionKey: context.User.Identity?.Name ?? context.Request.Headers.Host.ToString(), factory: partition => new FixedWindowRateLimiterOptions { AutoReplenishment = true, PermitLimit = 100, Window = TimeSpan.FromMinutes(1) })); }); // In controller [EnableRateLimiting("fixed")] [HttpPost("login")] public async Task> Login(LoginRequest request) { // Login logic } ``` ## Advanced authentication * Refresh tokens: You can have long-lived refresh tokens for automatic re-authentication, and short-lived access tokens for API calls * Secure token storage and rotation * OAuth 2.0 Integration * Social login with Google, Facebook, etc. * Third-party application authorization * Multi-factor authentication (MFA) * SMS/email-based verification, Authenticator app integration, backup codes... * API Key authentication * Alternative to JWT for API access * Key-based authentication for services with rate limiting per API key ## Resources * [JWT.io](https://jwt.io): JWT token decoder and debugger * [ASP.NET Core Security Documentation](https://docs.microsoft.com/en-us/aspnet/core/security/) * [OAuth 2.0 Specification](https://tools.ietf.org/html/rfc6749) * [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) * [C# corner: JWT Token creation tutorial](https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/) * [ASP.NET Core: Configure JWT bearer authentication](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/configure-jwt-bearer-authentication?view=aspnetcore-9.0) * Remember, if you don't restrict access to your endpoints, something like [this](https://mastodon.gamedev.place/@badlogic/) [might happen](https://firesky.tv/)