What Are JSON Web Tokens?
JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. The claims are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity-protected with a Message Authentication Code (MAC) and/or encrypted. In practical terms, a JWT is a string of three dot-separated segments that securely carries information between a client and a server.
JWTs have become the standard token format for modern web authentication and authorization. They are used in OAuth 2.0 flows, OpenID Connect, single sign-on (SSO) systems, API authentication, and many other security contexts. Unlike traditional session-based authentication, JWTs are stateless — the server does not need to store session data because all the necessary information is contained within the token itself. You can decode and inspect JWT tokens using our JWT Decoder tool to see exactly what claims they contain.
JWT Structure: Header, Payload, Signature
Header
The header is the first segment of the JWT and contains metadata about the token itself. It is a JSON object that typically specifies the token type (typ) and the signing algorithm (alg). For example, a common header looks like {"alg": "HS256", "typ": "JWT"}. The header is Base64URL-encoded to produce the first segment of the token. Additional header parameters can include the content type (cty), key identifier (kid), and others depending on the specific use case.
Payload
The payload is the second segment and contains the actual claims — the pieces of information being transmitted. Claims are name-value pairs where the names are strings and the values can be any JSON value. There are three types of claims: registered claims, which are predefined and provide useful information (such as iss for issuer, sub for subject, aud for audience, exp for expiration time, iat for issued at, and jti for a unique token identifier); public claims, which are defined by the JWT community using IANA JSON Web Token Registry; and private claims, which are custom claims agreed upon between the producing and consuming parties.
Signature
The signature is the third segment and is used to verify that the token has not been tampered with. It is created by taking the Base64URL-encoded header, concatenating it with a period and the Base64URL-encoded payload, and then signing the resulting string using the algorithm specified in the header. For HMAC-based algorithms (like HS256), the signing key is a shared secret. For RSA/ECDSA algorithms (like RS256), the signing key is a private key, and verification is done using the corresponding public key. Without the signature, anyone could modify the header or payload to impersonate another user or escalate privileges.
Signing Algorithms
HMAC (HS256, HS384, HS512)
HMAC algorithms use a shared secret key for both signing and verifying tokens. HS256 (HMAC using SHA-256) is the most commonly used HMAC algorithm and is suitable for most applications where the issuing and consuming services are the same or share a secret. The main advantage of HMAC is simplicity — you only need to manage a single secret key. However, this also means that if the secret is leaked, an attacker can both sign and verify tokens, which is a significant security risk. Always use a cryptographically strong secret of at least 256 bits.
RSA (RS256, RS384, RS512)
RSA algorithms use an asymmetric key pair — a private key for signing and a public key for verification. RS256 (RSA using SHA-256) is the most widely used RSA algorithm for JWTs. The key advantage is that the verification service only needs the public key, not the private key, which provides better security in distributed systems. Multiple services can verify tokens signed by a central authentication service without sharing any secrets. OAuth 2.0 and OpenID Connect providers like Auth0, Okta, and Firebase typically use RS256.
ECDSA (ES256, ES384, ES512)
ECDSA (Elliptic Curve Digital Signature Algorithm) provides similar asymmetric properties to RSA but with smaller key sizes and better performance. ES256 uses the P-256 curve with SHA-256 and produces significantly smaller signatures than RS256 for equivalent security. A 256-bit ECDSA key provides roughly the same security as a 3072-bit RSA key. ECDSA is increasingly popular in modern applications, especially in resource-constrained environments like mobile and IoT devices where key size and computational efficiency matter.
Authentication Flow with JWTs
A typical JWT authentication flow begins when a user submits their credentials (username and password) to the server. The server verifies the credentials, creates a JWT containing the user's claims (user ID, roles, permissions), signs it with the server's secret or private key, and returns the token to the client. The client stores the token (typically in memory or httpOnly cookies) and includes it in the Authorization header of subsequent requests, usually with the Bearer scheme: Authorization: Bearer eyJhbGciOi....
On each request, the server extracts the token from the header, verifies the signature, checks the expiration and other claims, and then processes the request if everything is valid. This stateless approach eliminates the need for server-side session storage, making it easy to scale horizontally across multiple servers. Each server can verify the token independently using the shared secret or public key, without querying a central session store.
Access Tokens vs Refresh Tokens
Most secure JWT implementations use two types of tokens. Access tokens are short-lived (typically 5 to 15 minutes) and are used for authenticating API requests. Their short lifespan limits the damage if a token is compromised — the attacker only has a brief window to use it. Refresh tokens are long-lived (days, weeks, or months) and are used to obtain new access tokens when the current one expires. They are stored more securely (typically in httpOnly cookies) and are only sent to a specific token endpoint, never included in general API requests.
When an access token expires, the client sends the refresh token to the server's refresh endpoint. The server verifies the refresh token, issues a new access token (and optionally a new refresh token), and returns them to the client. This flow provides a seamless user experience while maintaining strong security. If a refresh token is compromised, the server can revoke it, forcing the user to re-authenticate. Token rotation, where a new refresh token is issued with each refresh, adds another layer of security by detecting token theft.
Security Best Practices
- Validate all claims: Always verify the signature, check the expiration (
exp), confirm the issuer (iss) and audience (aud) match expected values, and check thenbf(not before) claim if used. Missing any of these checks can lead to security vulnerabilities. - Use strong secrets: For HMAC algorithms, use a cryptographically random secret of at least 256 bits (32 bytes). Never use default, predictable, or human-readable secrets. Rotate secrets periodically and have a rotation strategy in place.
- Keep access tokens short-lived: Access tokens should expire in 5 to 15 minutes. The shorter the lifespan, the less damage a compromised token can cause. Use refresh tokens for maintaining sessions.
- Store tokens securely: Do not store tokens in localStorage, as they are accessible to any JavaScript running on the page and vulnerable to XSS attacks. Prefer httpOnly, secure, SameSite cookies, or in-memory storage.
- Use HTTPS exclusively: JWTs are bearer tokens, meaning anyone who possesses the token can use it. Always transmit tokens over HTTPS to prevent interception by man-in-the-middle attacks.
- Prefer asymmetric algorithms for distributed systems:Using RS256 or ES256 with public key verification allows multiple services to validate tokens without sharing secrets, reducing the attack surface if any single service is compromised.
Common JWT Vulnerabilities
One of the most critical JWT vulnerabilities is the "none" algorithm attack. Some JWT libraries accepted the "alg": "none"header, which meant the token had no signature at all. An attacker could craft a token with the "none" algorithm and any claims they wanted, and the library would accept it as valid. This vulnerability has been patched in most modern libraries, but it highlights the importance of explicitly specifying allowed algorithms on the verification side and never trusting the algorithm declared in the token header.
Another common vulnerability is weak signing secrets. Developers sometimes use short, predictable, or default secrets for HMAC signing. Tools like jwt-cracker can brute-force weak HS256 secrets in seconds, allowing attackers to forge valid tokens. Additionally, confusion between symmetric and asymmetric algorithms can be exploited: if a server expects RS256 but the attacker sends an HS256 token signed with the public key (which the server uses for RS256 verification), the server might incorrectly validate the forged token. Always use a well-maintained JWT library and explicitly configure the expected algorithm.
JWT vs Session-Based Authentication
In traditional session-based authentication, the server creates a session after a successful login and stores the session data in a database or in-memory store like Redis. The client receives a session cookie that references the server-side session. This approach provides the server with full control — sessions can be revoked instantly by deleting the session data. However, it requires server-side storage and can complicate horizontal scaling unless a shared session store is used.
JWT-based authentication is stateless by design. The server does not store any session data; all necessary information is in the token itself. This makes horizontal scaling trivial and reduces database load. However, JWTs cannot be easily revoked before they expire (unless you maintain a blacklist), and they can carry more data per request since the token is sent with every HTTP call. For most modern applications, a hybrid approach works well: use JWTs for access tokens with short expiration times, and maintain a server-side list for revocation when needed.
Key Takeaways
- A JWT consists of three Base64URL-encoded segments: a header, a payload with claims, and a cryptographic signature.
- Common signing algorithms include HS256 (HMAC), RS256 (RSA), and ES256 (ECDSA), each with different security properties and trade-offs.
- Use short-lived access tokens (5-15 minutes) paired with refresh tokens for a secure and seamless authentication experience.
- Never trust the algorithm in the token header — always specify allowed algorithms on the verification side.
- Store tokens securely (httpOnly cookies or memory), always use HTTPS, and validate all claims including expiration, issuer, and audience.
- Use our JWT Decoder to inspect and debug your tokens during development.
Frequently Asked Questions
Can JWTs be decrypted?
The header and payload of a standard JWT are only Base64URL-encoded, not encrypted. Anyone who has the token can decode and read the claims. The signature ensures that the token has not been modified, but it does not hide the contents. If you need to protect sensitive claims from being read, you should use JSON Web Encryption (JWE), which encrypts the payload. However, JWTs should generally not contain sensitive information like passwords or credit card numbers, even when encrypted — they should only contain identifiers and claims needed for authorization.
How long should a JWT be valid?
Access tokens should be short-lived, typically 5 to 15 minutes. This limits the window of opportunity if a token is stolen. Refresh tokens can be longer-lived — anywhere from a few hours to several months, depending on your application's security requirements and user experience goals. For high-security applications, consider even shorter access token lifetimes (1-5 minutes) with more frequent silent refresh. The key principle is to minimize the useful lifetime of any single token.
What happens if a JWT is stolen?
If a JWT access token is stolen, the attacker can use it to make authenticated requests until it expires. This is why short expiration times are critical. If a refresh token is stolen, the attacker can obtain new access tokens indefinitely. To mitigate this, implement refresh token rotation (issuing a new refresh token each time the old one is used) and maintain a blacklist of compromised tokens. Using httpOnly, secure, SameSite cookies for token storage also helps protect against common theft vectors like XSS attacks.
Should I use JWTs or sessions for my application?
It depends on your requirements. JWTs are excellent for stateless architectures, microservices, and mobile or single-page applications where you want to avoid server-side session management. Sessions are better when you need fine-grained control over user state, immediate revocation, or when your application is a traditional server-rendered web app. Many applications use a hybrid approach: server-side sessions for the primary web interface and JWTs for API access from mobile or third-party clients.