JWT (JSON Web Token) authentication in ASP.NET Core, storing the token in an HTTP-only cookie for better security. This approach combines the benefits of JWT with the security advantages of cookie storage.
Why JWT with Cookies?
JWTs are popular for authentication because they're stateless and contain all necessary user information. However, storing JWTs in localStorage or sessionStorage can expose them to XSS (Cross-Site Scripting) attacks. By storing the JWT in an HTTP-only cookie, we mitigate this risk while maintaining the benefits of JWT authentication.
Step 1: Configure JWT Authentication
First, let's set up JWT authentication in our ASP.NET Core application:
var jwtKey = "qwertyuiopasdfghjklzxcvbnm123456";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "your-app",
ValidAudience = "your-app",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
};
// Read JWT token from cookie
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey("AuthToken"))
{
context.Token = context.Request.Cookies["AuthToken"];
}
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
Step 2: Configure Middleware
app.UseAuthentication();
app.UseAuthorization();
add the authentication and authorization middleware in your Program.cs:
Step 3: Generate and Store JWT Tokens
Here's our token generation endpoint:
[HttpPost("GenerateAuthToken")]
public IActionResult GenerateAuthToken()
{
var jwtKey = "qwertyuiopasdfghjklzxcvbnm123456";
var token = GenerateJwtToken("Test", jwtKey);
var cookieOptions = new CookieOptions
{
HttpOnly = true, // Prevent JavaScript access
Secure = true, // Only send over HTTPS
SameSite = SameSiteMode.Strict, // Prevent CSRF
Expires = DateTime.UtcNow.AddHours(1)
};
Response.Cookies.Append("AuthToken", token, cookieOptions);
return Ok(new { message = "Auth Token generated successfully" });
}
public static string GenerateJwtToken(string username, string key)
{
var tokenHandler = new JwtSecurityTokenHandler();
var keyBytes = Encoding.UTF8.GetBytes(key);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = "your-app",
Audience = "your-app",
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(keyBytes),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
Step 4: Protect Endpoints with Authorization
Now we can protect endpoints using the [Authorize] attribute:
[Authorize]
[HttpGet("GetCurrentProfile")]
public IActionResult GetCurrentProfile()
{
var username = User.Identity?.Name;
return Ok(new { user = username });
}
Full Example :
Here's a complete minimal API example:
var builder = WebApplication.CreateBuilder(args);
// Add services
var jwtKey = "qwertyuiopasdfghjklzxcvbnm123456";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "your-app",
ValidAudience = "your-app",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey("AuthToken"))
{
context.Token = context.Request.Cookies["AuthToken"];
}
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Security Considerations
Key Management: In production, use a more complex key and store it securely (e.g., in Azure Key Vault or AWS Secrets Manager)
Token Expiration: Keep token lifetimes short (1 hour in our example)
Refresh Tokens: Consider implementing a refresh token mechanism for longer sessions
Production Environment: Always use HTTPS in production