Authentication, JWT Tokens & Password Hashing Masterclass
Welcome to Phase 14 (Chapter 39): C# Authentication โ JWT Tokens, Password Hashing, Claims & Refresh Tokens Masterclass! Authentication verifies who the user is. In this chapter, we implement a complete auth system: user registration with BCrypt password hashing, login with JWT (JSON Web Token) generation, Claims-based identity, Refresh tokens for session extension, and token validation middleware.
| Concept | Question Answered | Mechanism |
|---|---|---|
| Authentication | "Who are you?" โ Proves the user's identity. | Login with username+password โ JWT Token issued |
| Authorization | "What can you do?" โ Controls what an authenticated user can access. | [Authorize(Roles = "Admin")] attribute on endpoints |
// Install BCrypt package
// dotnet add package BCrypt.Net-Next
// User Entity
public class User
{
public int Id { get; set; }
public string Email { get; set; } = "";
public string PasswordHash { get; set; } = ""; // NEVER store plaintext!
public string Role { get; set; } = "User";
}
// AuthService โ Registration
public async Task<User> RegisterAsync(string email, string password)
{
if (await _context.Users.AnyAsync(u => u.Email == email))
throw new InvalidOperationException("Email already registered.");
var user = new User
{
Email = email.ToLower(),
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password), // Salted hash
Role = "User"
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return user;
}
// Login โ Verify password
public bool VerifyPassword(string inputPassword, string storedHash)
=> BCrypt.Net.BCrypt.Verify(inputPassword, storedHash);
// dotnet add package System.IdentityModel.Tokens.Jwt
// dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
public string GenerateJwtToken(User user)
{
var jwtKey = _config["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured!");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Email, user.Email),
new Claim(ClaimTypes.Role, user.Role)
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
// Program.cs โ Configure JWT Authentication Middleware
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateLifetime = true
};
});
Q1: What is a JWT Refresh Token?
A Refresh Token is a long-lived, opaque token stored securely (in an HttpOnly cookie or database) used to obtain new short-lived JWT Access Tokens after they expire. This avoids forcing users to re-login while keeping Access Tokens short-lived for security.
Q2: Why is BCrypt preferred over MD5/SHA1 for passwords?
BCrypt automatically salts and hashes passwords and has a configurable work factor that makes brute-force attacks computationally expensive. MD5/SHA1 are fast hashing algorithms not designed for password storage and are vulnerable to rainbow table attacks.