.NETHard
JWT authentication in ASP.NET Core — full implementation walkthrough
JWT is a self-contained signed token: header.payload.signature, base64url-encoded.
Server setup
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
opts.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = cfg["Jwt:Issuer"],
ValidAudience = cfg["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(cfg["Jwt:Key"]!)),
ClockSkew = TimeSpan.FromSeconds(30)
};
});
builder.Services.AddAuthorization();
Issuing tokens
[HttpPost("login")]
public IActionResult Login(LoginRequest req)
{
var user = _users.Verify(req.Email, req.Password);
if (user is null) return Unauthorized();
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(ClaimTypes.Role, user.Role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_cfg["Jwt:Key"]!));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _cfg["Jwt:Issuer"], audience: _cfg["Jwt:Audience"],
claims: claims, expires: DateTime.UtcNow.AddMinutes(15),
signingCredentials: creds);
return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
}
Why short-lived access + refresh tokens
15-minute access token + opaque refresh token stored server-side. If a token leaks, blast radius is bounded; the refresh token can be revoked on the server.
Production checklist
- Key minimum 256 bits for HS256. Prefer RS256 with cert rotation in multi-service systems so verifiers don't need the signing secret.
[Authorize(Roles = "admin")]works once the role claim mapping is correct.- Never put PII or large payloads in the token; clients send it on every request.
- Use
httpOnly; secure; samesite=strictcookies for browser clients instead of localStorage to prevent XSS theft.
Common mistake
Storing JWT in localStorage and reading it from JavaScript. Any XSS pwns the entire session. Cookies + CSRF tokens, or BFF pattern with session cookies, is safer for browser apps.