JWT Security Risks and Mitigations – Java Backend
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
A JWT is safe only when the whole verification and lifecycle design is safe. The API must use a maintained JOSE library, restrict verification to the expected algorithms and key types, obtain keys from a trusted issuer, and validate issuer, audience, expiry, not-before, and any claims used for authorization. Simply decoding the payload is not authentication.
For example, an Orders API should reject a correctly signed token whose aud names the Billing API, and it should not accept an attacker-selected alg just because the token header requests it. A signed JWT is readable, so it should carry only the claims the API needs; passwords and other secrets do not belong in it.
A stolen access token can be replayed until it expires or a server-side check rejects it. Bounded access-token lifetime reduces that window, while refresh-token rotation with reuse detection can limit refresh replay. Systems that need immediate logout or permission changes need a revocation, token-version, or introspection design. Those controls add state deliberately; JWT does not remove the need to manage security state.
JWT Security Risks and Mitigations – Java Backend
Direct Answer
JWT failures usually come from incorrect verification, exposed signing keys, readable sensitive claims, stolen tokens, stale authorization, and weak expiry or refresh design. Use a maintained JOSE library, allow only expected algorithms and key types, obtain keys from a trusted issuer, validate issuer, audience and time claims, keep claims minimal, use TLS, and choose bounded lifetimes from the system's risk. Plan refresh-token rotation, reuse detection, or server-side revocation where immediate invalidation is required.
Code Example
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation("https://auth.example");
OAuth2TokenValidator<Jwt> issuerAndTime =
JwtValidators.createDefaultWithIssuer("https://auth.example");
OAuth2TokenValidator<Jwt> audience = token ->
token.getAudience().contains("orders-api")
? OAuth2TokenValidatorResult.success()
: OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token", "Wrong audience", null));
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(issuerAndTime, audience));
return decoder;
}
The decoder verifies the issuer's keys and standard time claims; the added validator prevents a token for another API from being reused here.
Common Mistakes & Pitfalls
- Not validating the algorithm in the header: Freshers call parseClaimsJws() but don't realize some libraries accept alg:none if not explicitly blocked. Always use parserBuilder().setSigningKey() which enforces signature validation — never parse the token manually with string splitting.
- Storing the secret key in application.properties committed to Git: The signing key should be an environment variable or secret manager entry, never hardcoded or version-controlled. Use at minimum a 256-bit random key for HS256.
- Setting expiration to days or weeks: A 7-day token gives attackers a week-long attack window if stolen. Use 15-30 minute access tokens with a separate refresh token for longer sessions.
- Implementing blacklist in application memory: Using a simple HashSet means the blacklist is lost on server restart and isn't shared across instances. Use Redis or a database for distributed blacklist storage.
Check your understanding
Answer these without looking back at the article.
- What is the 'alg:none' JWT attack in detail?
- Explain the refresh token pattern — how do access tokens and refresh tokens work together?
- What is the difference between HS256 and RS256 in terms of security model?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.