TL;DR
Yes, there are standard ways to use JWTs (JSON Web Tokens) for user authentication. This guide covers the most important protocols and techniques to keep your system secure. We’ll focus on OAuth 2.0, OpenID Connect, and best practices like token storage, refresh tokens, and revocation.
1. Understanding the Basics
JWTs aren’t authentication *protocols* themselves; they are a method of representing claims securely. You need a protocol to handle the initial login and token exchange. The most common choices are:
- OAuth 2.0: Primarily for authorisation (allowing access to resources), but often used with JWTs as access tokens.
- OpenID Connect (OIDC): Built on top of OAuth 2.0, specifically designed for authentication and identity management. It provides a standardised way to verify user identity.
2. Using OAuth 2.0 with JWTs
OAuth 2.0 defines several ‘grant types’ for obtaining tokens. Here’s how it works with JWTs:
- Authorisation Code Grant: The most secure flow, especially for web applications.
- User is redirected to the Authorisation Server (e.g., Google, Facebook).
- User authenticates with the server and grants permission.
- The server redirects back to your application with an authorisation code.
- Your application exchanges this code for access tokens (often JWTs) and refresh tokens.
- Implicit Grant: Less secure, generally discouraged due to security concerns. It directly returns the token in the redirect URL.
- Client Credentials Grant: For machine-to-machine authentication (e.g., services accessing other services).
Example of exchanging an authorisation code for a JWT:
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code={AUTHORISATION_CODE}&redirect_uri={YOUR_REDIRECT_URI}&client_id={YOUR_CLIENT_ID}&client_secret={YOUR_CLIENT_SECRET}
3. OpenID Connect (OIDC) – The Preferred Approach
OIDC adds an identity layer to OAuth 2.0. It provides a standard way to get user profile information.
- ID Token: A JWT containing claims about the authenticated user (name, email, etc.).
- Userinfo Endpoint: Provides additional user details.
OIDC simplifies authentication and provides better security features than using OAuth 2.0 alone.
4. JWT Storage Best Practices
- Never store JWTs in LocalStorage: Vulnerable to XSS attacks.
- Use HttpOnly Cookies (with SameSite attribute): The most secure option for web applications. Set
HttpOnlyto prevent JavaScript access andSameSite=StrictorSameSite=Laxto mitigate CSRF risks.Set-Cookie: jwt_token=YOUR_JWT; HttpOnly; SameSite=Strict; Secure - In-Memory Storage (for short-lived tokens): Suitable for single-page applications, but requires careful handling of token persistence.
- Secure Enclaves/Keychains: For mobile apps, use platform-specific secure storage mechanisms.
5. Refresh Tokens
JWTs have a limited lifespan. Use refresh tokens to obtain new access tokens without requiring the user to re-authenticate.
- Store refresh tokens securely: Database is recommended, with proper encryption and protection against theft.
- Rotate refresh tokens: Issue a new refresh token each time one is used, invalidating the old one. This limits the impact of a compromised token.
- Short Refresh Token Lifespan: Keep them short-lived to reduce risk.
6. JWT Revocation
Sometimes you need to invalidate tokens before they expire (e.g., user logs out, password change).
- Blacklisting: Store revoked token IDs in a database and check against them during validation.
- Token Expiration Time: The most common method; simply wait for the token to expire.
- Refresh Token Revocation: Revoking a refresh token invalidates all access tokens issued from it.
7. JWT Validation
- Verify Signature: Ensure the token hasn’t been tampered with using your secret key or public key (depending on the signing algorithm).
- Check Expiration Time (
expclaim): Reject expired tokens. - Validate Issuer (
issclaim) and Audience (audclaim): Confirm the token is intended for your application.
Example validation using a library:
// Example with a hypothetical JWT library
try {
const decoded = jwtLibrary.verify(token, secretKey);
if (decoded && decoded.exp > Date.now() / 1000) {
// Token is valid
} else {
// Invalid token
}
} catch (error) {
// Validation failed
}
8. Security Considerations
- Keep your secret key confidential: Never commit it to version control!
- Use strong signing algorithms: RS256 or ES256 are recommended. Avoid weak algorithms like HS256 if possible.
- Protect against brute-force attacks: Implement rate limiting and account lockout mechanisms.

