Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
JWT handling in .NET usually starts with the same string, but there are two very different jobs you can do with it. One job is decoding, which only parses the token so you can inspect claims and headers. The other is verification, which checks whether the token is trustworthy enough to drive authentication or authorization decisions.
Decoding Is Parsing, Not Trust
JwtSecurityTokenHandler.ReadJwtToken is useful when you want to inspect a token during debugging or log analysis. It reads the header and payload, but it does not prove that the signature is valid or that the token was issued for your API.
That output is convenient, but it is still untrusted input. A forged token can be decoded just as easily as a valid one. If your code reads a decoded role claim and immediately grants access, the signature check has effectively been skipped.
Validate With Explicit Rules
Verification happens through ValidateToken. The key point is that validation should reflect your actual security policy, not a vague set of defaults. For most APIs, that means checking the signing key, issuer, audience, and expiration time together.
Small configuration mistakes matter here. If the API accepts a token for the wrong audience, the signature may still be correct while the token is still wrong for your service. That is why audience and issuer checks belong in the same validation block as the signature.
Check What You Expect After Validation
A successful validation gives you a ClaimsPrincipal, but it is still worth checking for claim shape and token metadata that your application depends on. Some teams also verify the expected signing algorithm after the cryptographic check so that changes in issuer configuration do not silently weaken assumptions.
This second layer is not a replacement for signature verification. It is there to keep the application strict about what a valid identity must contain before business logic runs.
Centralize Validation in ASP.NET Core
Manual calls to ValidateToken are fine for small utilities, but production APIs usually want one shared configuration path. In ASP.NET Core, the normal approach is to configure JWT bearer authentication once and let middleware populate the authenticated user for each request.
That keeps every endpoint on the same policy. It also makes testing easier, because failure cases such as expired tokens or invalid issuers behave consistently everywhere instead of depending on hand-written controller code.
Handle Keys Like a Deployment Concern
Token verification is only as strong as the key management behind it. A local demo can read a symmetric secret from configuration, but a real system usually loads secrets or public keys from a secure store and supports rotation. If key rotation is part of the issuer design, your service should tolerate overlap periods where both old and new keys are valid.
Testing should cover more than the happy path. Include expired tokens, wrong audiences, tampered signatures, and missing claims. Those cases often catch the accidental weakening of TokenValidationParameters during future refactors.
Common Pitfalls
Decoding a JWT and treating its claims as trusted data is the most common failure. Another frequent mistake is validating the signature but disabling issuer or audience checks because setup feels inconvenient. Teams also get into trouble when secrets are hardcoded, clock skew is set too high, or claim requirements are enforced loosely and silently defaulted.
Summary
- Decoding only parses a JWT; it does not prove trust.
- Use
ValidateTokenwith explicit issuer, audience, lifetime, and signing key rules. - Treat claim presence and expected algorithm as part of application-level validation.
- Centralize validation in ASP.NET Core so every endpoint follows the same policy.
- Test failure paths, not just successful authentication.

