Issuing and validating JWT access tokens in ASP.NET Core
This guide walks through issuing signed 15-minute access tokens for ASP.NET Core Identity users with JsonWebTokenHandler and validating them strictly for issuer, audience, lifetime and signature on the JwtBearer side. It also flags the common traps: claim type mapping that hides sub, calling AddIdentity in an API, and authentication middleware registered in the wrong order.
Key points
- Issuing and validating go through the same JsonWebTokenHandler, which JwtBearer has used by default since .NET 8
- HS256 needs at least a 32-byte key; shorter keys fail at token creation with IDX10720, so validate length at startup
- Access tokens expire in 15 minutes and validation allows a 30-second clock skew alongside strict issuer, audience and lifetime checks
- A JWT is signed, not encrypted: anyone can read the claims, so keep secrets and personal data out of them
- Turn off claim type mapping or the raw sub claim is hidden and ICurrentUser.UserId comes back empty
- Skipping base.OnModelCreating(builder) in IdentityDbContext<AppUser> makes EF Core fail with a missing primary key
Starting from a scenario where an Angular app and a mobile client share one Store.Api, the guide builds registration, login and endpoint protection on ASP.NET Core Identity. Tokens are created with JsonWebTokenHandler, the same handler JwtBearer has used for validation since .NET 8, so both sides stay on one code path. Because HS256 needs a key of at least 32 bytes, the article checks key length at startup and keeps the secret in user secrets or an environment variable rather than appsettings.json.
The token carries sub, email, role and jti claims, lives for 15 minutes, and validation allows a 30-second clock skew alongside strict issuer and audience checks. A key caveat: a signed JWT is not encrypted, so anyone holding it can read the claims and no sensitive data belongs there. Services get an ICurrentUser abstraction instead of touching HttpContext, which only works once claim type mapping is switched off so the raw sub claim stays readable.
Real-world use
- A multi-client API serving both a web and a mobile app, where cookies do not work and Bearer tokens are required
- Layered applications where services such as order placement or auditing need the caller's identity without depending on HttpContext
- An auth service that reads time from TimeProvider so expiry can be tested with FakeTimeProvider instead of waiting
- A login flow that counts failed attempts and locks an account for 15 minutes after five failures
Why it matters
It collects the mistakes teams most often make when wiring authentication for the first time, from claim mapping to middleware order to short signing keys, all of which show up in production as 401s or security gaps.
Related stories
- ASP.NET Core Authentication & Authorization Explained: What to Use, When, and How to Ship It SafelyDEV Community: #aspnetcore3 d ago
- Role-based access to reports and folders in ASP.NET Core: users, roles and admin claimsDEV Community: #aspnetcore1 d ago
- Telr payments in ASP.NET Core: the parts the docs skipDEV Community: #aspnetcore1 d ago