JWT Tokens and How They Work Internally
JWT (JSON Web Token) is an open standard (RFC 7519) used for securely transmitting information between two parties as a JSON object. JWT is widely used for authentication and authorization purposes because it is stateless, compact, and easy to verify.
1. Structure of JWT
A JWT token consists of three parts separated by periods:
- Header: Contains metadata about the token, including the type of token (JWT) and the hashing algorithm (e.g., HMAC, SHA256).
- Payload: Holds the claims, or information, such as user ID and roles.
- Registered Claims: Predefined claims, e.g.,
iss(issuer),sub(subject), andexp(expiration). - Public Claims: Custom claims that are not reserved.
- Private Claims: Custom claims agreed upon between parties.
- Registered Claims: Predefined claims, e.g.,
- Signature: Ensures that the token was not tampered with and verifies the authenticity.
Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0Iiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2. Steps for JWT Token Generation and Validation
- Token Creation: The server creates a token when a user logs in, encoding the header and payload, and hashing them using a secret key.
- Token Transmission: The server sends the JWT token to the client (usually in the response).
- Token Validation: The client sends the JWT in the
Authorizationheader as a Bearer token. The server decodes, checks the signature, and validates claims like expiration.
JWT in the Authorization Header
Authorization: Bearer <JWT_TOKEN>
Pros of JWTs
- Stateless: No need to store sessions on the server.
- Compact: Lightweight and fast to transmit.
- Self-contained: Contains all necessary information about the user.
- Cross-platform: Compatible with many languages and frameworks.
JWT Algorithm & How Tokens Are Generated
1. JWT Algorithms
- HS256 (HMAC with SHA-256): Uses a secret key. Symmetric (same key for signing and verifying).
- RS256 (RSA Signature with SHA-256): Uses a public-private key pair. Asymmetric (more secure for distributed applications).
2. Generating JWT Tokens
Example: Generating a JWT Using HS256
{
"user_id": "12345",
"role": "admin",
"exp": 1704067199
}- Encoding: The payload and header are Base64Url-encoded.
- Signature Generation:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), my_secret_key )
Example Code: Generating and Verifying JWT in .NET
Generating a JWT:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Text;
public string GenerateJwtToken(string userId, string role, string secretKey)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim("role", role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "my_app",
audience: "my_app",
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}Verifying a JWT:
public ClaimsPrincipal ValidateJwtToken(string token, string secretKey)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(secretKey);
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
try
{
var principal = tokenHandler.ValidateToken(token, validationParameters, out _);
return principal;
}
catch (Exception)
{
return null; // Token is invalid or expired
}
}Best Practices and Common Pitfalls of JWT
1. Best Practices
- Use Strong Secret Keys: Ensure the secret key is long, complex, and stored securely.
- Use Asymmetric Algorithms (e.g., RS256): For distributed applications.
- Limit the Claims in the Payload: Only include essential information.
- Set Expiration Times: Use short-lived tokens (minutes/hours).
- Implement Refresh Tokens: For long-lived sessions.
- Store Tokens Securely: Use secure, HTTP-only cookies.
- Validate All Claims: Check
iss,aud, andexpclaims. - Monitor for JWT Revocation: Implement a revocation list.
2. Common Pitfalls
- Overly long token expiration times
- Including sensitive data in the payload
- Lack of signature validation
- Using weak signing algorithms (e.g.,
none) - Not using HTTPS
JWT Authentication and Authorization in ASP.NET Core Web API
1. Setting Up JWT Authentication
Step 1: Install NuGet Packages
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearerStep 2: Add JWT Settings to appsettings.json
"JwtSettings": {
"SecretKey": "Your_Secret_Key_Here",
"Issuer": "my_app",
"Audience": "my_app_audience"
}Step 3: Configure Authentication in Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var secretKey = jwtSettings.GetValue<string>("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.GetValue<string>("Issuer"),
ValidAudience = jwtSettings.GetValue<string>("Audience"),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
});
builder.Services.AddAuthorization();2. Protecting Endpoints with JWT Authorization
[ApiController]
[Route("api/[controller]")]
public class SecureDataController : ControllerBase
{
[HttpGet("secure-info")]
[Authorize]
public IActionResult GetSecureInfo()
{
return Ok("This is a secure endpoint only accessible to authenticated users.");
}
[HttpGet("admin-data")]
[Authorize(Roles = "Admin")]
public IActionResult GetAdminData()
{
return Ok("This is an admin-protected endpoint.");
}
}3. Generating JWT Tokens for Users
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
if (model.Username == "testuser" && model.Password == "password")
{
var token = GenerateJwtToken(model.Username);
return Ok(new { Token = token });
}
return Unauthorized("Invalid credentials");
}
private string GenerateJwtToken(string username)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var secretKey = jwtSettings.GetValue<string>("SecretKey");
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim("role", "User"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: jwtSettings.GetValue<string>("Issuer"),
audience: jwtSettings.GetValue<string>("Audience"),
claims: claims,
expires: DateTime.UtcNow.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}Refresh Tokens
1. What is a Refresh Token?
A refresh token is a long-lived token issued alongside the JWT, allowing the client to request a new JWT when it expires without requiring the user to re-authenticate.
2. How Refresh Tokens Work
- User Authenticates: Server issues both a JWT and a refresh token.
- Using JWT: Client uses the JWT to access protected resources.
- Token Expiry and Refresh: When the JWT expires, the client sends the refresh token to obtain a new JWT.
- Logout: Refresh token is invalidated.
3. Implementing Refresh Tokens
Refresh Token Model:
public class RefreshToken
{
public string Token { get; set; }
public string UserId { get; set; }
public DateTime ExpiryDate { get; set; }
public bool IsRevoked { get; set; }
}Generate Refresh Token:
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}Refresh Token Endpoint:
[HttpPost("refresh-token")]
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
{
var savedToken = GetStoredRefreshToken(request.RefreshToken);
if (savedToken == null || savedToken.IsRevoked || savedToken.ExpiryDate < DateTime.UtcNow)
{
return Unauthorized("Invalid or expired refresh token.");
}
var newJwtToken = GenerateJwtToken(savedToken.UserId);
var newRefreshToken = GenerateRefreshToken();
UpdateStoredRefreshToken(savedToken, newRefreshToken);
return Ok(new { Token = newJwtToken, RefreshToken = newRefreshToken });
}4. Security Best Practices for Refresh Tokens
- Store refresh tokens securely in HTTP-only cookies.
- Rotate tokens each time a JWT is refreshed.
- Limit token scope to only requesting new JWTs.
- Keep JWTs short-lived.
- Implement logout to invalidate refresh tokens.
Key Points to Remember
- JWT Structure: Header, payload, signature — ensures data integrity.
- Role of Refresh Tokens: Extend session duration without frequent re-authentication.
- Secure JWT and Refresh Tokens: Use best practices to store, validate, and revoke tokens.
- Code Flow: Know how to generate JWTs, secure endpoints, and implement refresh logic.
- ASP.NET Core Integration: Master setup including authentication, authorization, and token validation.