JSON Web Tokens
Introduction#
Authentication and authorization are fundamental challenges in modern web applications. How do you verify user identity across distributed services? How do you grant granular permissions without constant database lookups? How do you maintain security while enabling stateless architectures?
JSON Web Tokens (JWTs) emerged as a solution to these problems, providing a standardized way to represent claims securely between parties. Unlike traditional session-based authentication that requires server-side storage, JWTs are self-contained tokens that carry authentication and authorization information directly within the token itself.
Understanding JWT structure and proper usage is essential for building secure, scalable applications. These tokens enable stateless authentication but require careful implementation to avoid common security pitfalls.
JWT Structure and Format#
A JSON Web Token consists of three parts separated by dots: header, payload, and signature. Each part is Base64URL encoded, creating a compact, URL-safe token that can be easily transmitted in HTTP headers or query parameters.
Complete JWT Example#
Here’s what an actual JWT looks like in its Base64 encoded form:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
When decoded, these three parts reveal their JSON structure:
Header (Algorithm & Token Type):
{
"alg": "HS256",
"typ": "JWT"
}
Payload (Claims):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"exp": 1516242622
}
Signature (Verification):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
This structure enables JWTs to be self-contained, carrying all necessary authentication and authorization information without requiring server-side lookups. The Base64URL encoding makes them safe for transmission in URLs while keeping them compact and efficient.
Important Security Note: The JWT payload is only Base64 encoded, not encrypted. Anyone who receives a JWT can decode and read its contents. Never include sensitive information like passwords, social security numbers, or private keys in JWT payloads. The signature prevents tampering but does not provide confidentiality.
Header: Algorithm and Token Type#
The header contains metadata about the token, specifically the signing algorithm and token type. In our example, "alg": "HS256" specifies HMAC SHA256 signing, while "typ": "JWT" identifies this as a JWT token.
Payload: Claims and Data#
The payload contains claims about the user and token. In our example:
"sub": "1234567890"- Subject (user ID)"name": "John Doe"- User’s name"admin": true- Custom claim for permissions"iat": 1516239022- Issued at time (Unix timestamp)"exp": 1516242622- Expiration time (Unix timestamp)
Standard claims like sub, iat, and exp provide consistent ways to handle user identity and token lifecycle. Custom claims like admin enable application-specific authorization without database lookups.
Signature: Integrity and Authenticity#
The signature ensures token integrity and authenticity. It’s created by encoding the header and payload, then signing the result with a secret key. Recipients can verify the signature to ensure the token hasn’t been tampered with and comes from a trusted source.
Using JWTs in Applications#
JWTs work well for stateless authentication scenarios where you need to verify user identity without server-side session storage. Here are the most common implementation patterns.
API Authentication#
APIs commonly use JWTs in the Authorization header with Bearer authentication. Your TypeScript client code might look like this:
// Client-side API call
const response = await fetch('/api/users', {
headers: {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json'
},
});
On the server side, you extract and validate the token for each request. In C# ASP.NET Core, this happens automatically with proper JWT middleware configuration.
Single Sign-On (SSO)#
JWTs enable SSO by allowing multiple applications to trust tokens issued by a central authentication service. Users log in once and receive a JWT that works across all company applications. Each application validates the token independently using a shared public key.
Microservice Authentication#
In microservice architectures, JWTs enable service-to-service authentication without requiring each service to contact a central authentication server. Services can validate incoming JWTs using shared keys or public key cryptography, making the architecture more resilient and performant.
Mobile and SPA Applications#
Single-page applications and mobile apps benefit from JWTs because tokens can be stored locally and include expiration information. This reduces authentication-related network requests and enables offline functionality where appropriate.
Security Considerations#
JWTs introduce specific security challenges that developers must understand and address. The self-contained nature that makes JWTs useful also creates unique risks that require careful handling.
Token Storage: localStorage vs httpOnly Cookies#
Where you store JWTs significantly impacts application security. Browser-based applications face a critical choice between localStorage and httpOnly cookies, each with distinct security implications.
localStorage provides persistent storage and easy JavaScript access, but exposes tokens to Cross-Site Scripting (XSS) attacks. If an attacker injects malicious JavaScript into your application, they can read tokens from localStorage and impersonate users. However, localStorage doesn’t automatically send tokens with requests, protecting against Cross-Site Request Forgery (CSRF) attacks.
httpOnly cookies protect against XSS attacks because JavaScript cannot access them, but they automatically accompany HTTP requests, making them vulnerable to CSRF attacks. Browsers send httpOnly cookies with every request to the domain, potentially allowing malicious sites to trigger authenticated actions.
For maximum security, use httpOnly cookies with SameSite=Strict and implement CSRF protection. This combination protects against both XSS and CSRF attacks while maintaining usability.
Algorithm Selection and Security Implications#
JWT signing algorithms have different security properties and use cases. Understanding these differences helps choose the right algorithm for your application.
HMAC (HS256, HS384, HS512) uses symmetric cryptography with shared secrets. Both the token issuer and all verifiers must know the same secret key. This approach offers excellent performance and simple implementation but creates key distribution challenges in distributed systems.
RSA (RS256, RS384, RS512) uses asymmetric cryptography with public-private key pairs. The issuer signs tokens with their private key, while verifiers only need the public key. This enables distributed verification without sharing secrets but requires certificate management and has higher computational overhead.
ECDSA (ES256, ES384, ES512) also uses asymmetric cryptography but with elliptic curve keys, providing similar security to RSA with smaller key sizes and better performance.
Choose HMAC for simple applications with centralized authentication. Choose RSA or ECDSA for distributed systems where multiple services need to verify tokens without accessing shared secrets.
Token Expiration and Refresh Patterns#
JWT expiration strategy balances security and usability. Short-lived tokens limit damage if compromised but require frequent renewal. Long-lived tokens improve user experience but extend vulnerability windows.
A common pattern uses short-lived access tokens (15-30 minutes) with longer-lived refresh tokens (days or weeks). This approach provides security benefits while maintaining good user experience.
Common Security Vulnerabilities#
Several JWT-specific attacks have emerged that developers must guard against:
“None” algorithm attack: Some libraries accept tokens with "alg": "none", effectively removing signature verification. Always validate the algorithm field and reject unexpected algorithms.
Algorithm confusion: Attackers may try to trick verifiers into treating RSA tokens as HMAC tokens, allowing them to sign tokens using public keys. Implement strict algorithm validation.
Sensitive data exposure: Remember that Base64 encoding is not encryption. JWTs can be decoded by anyone, so never include passwords, social security numbers, or other sensitive data in payloads.
Missing expiration validation: Always validate the exp claim to ensure tokens haven’t expired. Consider implementing nbf (not before) validation for tokens that shouldn’t be valid immediately.
Implementation Best Practices#
Proper JWT implementation requires careful attention to security, performance, and maintainability. These practices help ensure your JWT usage remains secure and efficient.
Library Selection and Configuration#
Use well-established JWT libraries rather than implementing JWT handling manually. For TypeScript/JavaScript, consider jose or jsonwebtoken. For C#, use System.IdentityModel.Tokens.Jwt or ASP.NET Core’s built-in JWT middleware.
Configure libraries to reject dangerous algorithms and validate required claims.
Payload Design Guidelines#
Keep JWT payloads minimal and focused. Include only information needed for authentication and authorization decisions. Avoid large objects or sensitive data that shouldn’t be exposed to clients.
Use standard claims when possible: sub (subject), iss (issuer), aud (audience), exp (expiration), iat (issued at). Add custom claims sparingly and consider token size implications.
Error Handling and Validation#
Implement robust error handling for JWT validation failures. Different error types should be handled appropriately while avoiding information leakage to potential attackers.
Always validate expiration times, required claims, and algorithm types. Log authentication failures for security monitoring, but avoid logging complete tokens or sensitive information.
When to Use JWTs#
JWTs work best in specific scenarios where their stateless nature provides clear advantages. Understanding these trade-offs helps determine when JWTs are the right choice.
JWTs vs. Session-Based Authentication#
Traditional session-based authentication stores user state on the server and uses session identifiers in cookies. This approach provides immediate revocation capabilities and keeps sensitive information server-side, making it ideal for applications requiring strong security controls.
JWTs trade some security guarantees for scalability and simplicity. They eliminate server-side session storage and enable stateless architectures, but you cannot immediately revoke JWTs without additional infrastructure. Choose JWTs when scalability and stateless design outweigh immediate revocation needs.
JWTs vs. API Keys#
API keys provide simpler authentication for service-to-service communication. They’re easy to rotate, can be immediately revoked, and don’t carry embedded information. However, they require database lookups for validation and don’t carry user context.
JWTs excel when you need to include user or permission information in the token itself, reducing database queries during request processing. The self-contained nature improves performance but complicates revocation.
Ideal JWT Use Cases#
JWTs work particularly well for:
- Microservice architectures - Services need to verify user identity without central authentication calls.
- Single-page applications - Applications that need to store authentication state client-side.
- API authentication - Stateless validation improves performance and scalability.
- Cross-domain authentication - Tokens need to work across different origins.
- Mobile applications - Applications that benefit from offline-capable authentication tokens.
Avoid JWTs when you need immediate token revocation, when handling highly sensitive data that shouldn’t be client-accessible, or when token size becomes a performance concern due to large payloads.
Understanding these trade-offs enables informed architectural decisions that balance security, performance, and operational complexity for your specific requirements.