add lecture 7 initial version, update readme
parent
0ecd662086
commit
cc239a6c58
File diff suppressed because one or more lines are too long
@ -1,16 +1,749 @@
|
||||
# Authentication & Authorization
|
||||
---
|
||||
marp: true
|
||||
paginate: true
|
||||
math: mathjax
|
||||
theme: buutti
|
||||
title: 7. Authentication and Authorization
|
||||
---
|
||||
|
||||
__Kato täältä esimerkki__
|
||||
# Authentication and Authorization
|
||||
|
||||
* Authentication: Verifying that you are indeed you
|
||||
* Authorization: Determining access rights based on your privileges
|
||||
* If you don't restrict access to your endpoints, something like [this](https://mastodon.gamedev.place/@badlogic/111246798083590676) [might happen](https://firesky.tv/) !!!
|
||||
* Tee loput luennot tän esimerkin mukaan:
|
||||
* [https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/](https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/)
|
||||
<!-- headingDivider: 3 -->
|
||||
<!-- class: invert -->
|
||||
|
||||
Esimerkkisisältö: Tehtävänanto
|
||||
## Contents
|
||||
|
||||
* Make a simple HTML website using the template from the previous slide, so that it contains paragraphs, headings, images and links.
|
||||
* Add at least two links; one remote and one local.
|
||||
* How would you make an image to be a link?
|
||||
* Later on, we will add more stuff to the site.
|
||||
- [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)
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
#### 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
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
#### Custom claim examples
|
||||
|
||||
* `userId`: User's unique identifier
|
||||
* `email`: User's email address
|
||||
* `role`: User's role (admin, user, etc.)
|
||||
* `permissions`: Specific permissions
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
## 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
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
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<ActionResult<AuthResponse>> 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<ActionResult<AuthResponse>> 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
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
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<UserProfile> 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
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
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<List<User>> 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
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
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
|
||||
|
||||
<div class='columns' markdown='1'>
|
||||
<div markdown='1'>
|
||||
|
||||
* Register a new user:
|
||||
```
|
||||
POST /api/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
<div markdown='1'>
|
||||
|
||||
* 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 <your-jwt-token>
|
||||
```
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### 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
|
||||
<!--_class: "exercise invert" -->
|
||||
|
||||
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<HttpContext, string>(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<ActionResult<AuthResponse>> 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/)
|
Loading…
Reference in New Issue