Understanding JWT Tokens

JSON Web Tokens (JWTs) are a compact, URL-safe way to represent claims securely between parties. They're widely used for authentication in modern web applications and APIs. This guide explains JWT structure, how signing and verification work, security best practices, and when to use JWTs versus other authentication methods.

What is a JWT?

A JSON Web Token (JWT) is a self-contained token format that encodes information as a JSON object and signs it to ensure authenticity. JWTs are commonly used for authentication: after logging in, users receive a JWT that they include in subsequent requests to prove their identity without re-authenticating.

JWTs are stateless, meaning servers don't need to store session data in databases or memory. All necessary information (user ID, roles, expiration) is contained within the token itself. This makes JWTs ideal for distributed systems, microservices, and APIs where session storage would create bottlenecks.

Example JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT structure: Header, Payload, and Signature

A JWT consists of three parts separated by periods (.):

Header.Payload.Signature

1. Header

The header specifies the token type (JWT) and the signing algorithm (e.g., HMAC SHA256 or RSA). It's a JSON object Base64URL-encoded.

Example header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Base64URL encoded: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload

The payload contains claims—statements about the user and additional metadata. Claims are categorized as:

  • Registered claims: Predefined standard claims like iss (issuer), exp (expiration), sub (subject/user ID), iat (issued at).
  • Public claims: Custom claims defined by applications, like role, email, or permissions.
  • Private claims: Custom claims agreed upon by parties sharing the token.

Example payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "admin"
}

Base64URL encoded: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjIsInJvbGUiOiJhZG1pbiJ9

3. Signature

The signature ensures the token hasn't been tampered with. It's created by taking the encoded header and payload, combining them with a secret key, and applying the specified algorithm.

Signing process (HMAC SHA256):

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Result: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The signature allows recipients to verify the token was signed by someone with the secret key and that the header/payload haven't been modified.

How JWT authentication works

  1. User logs in: User submits credentials (username/password) to the server.
  2. Server verifies credentials: Server checks credentials against the database.
  3. Server generates JWT: If credentials are valid, the server creates a JWT containing user ID, roles, and expiration, signs it with a secret key, and returns it to the client.
  4. Client stores JWT: Client stores the JWT (typically in memory, localStorage, or a cookie).
  5. Client includes JWT in requests: For subsequent API requests, the client includes the JWT in the Authorization header: Authorization: Bearer <token>.
  6. Server validates JWT: Server extracts the JWT, verifies the signature, checks expiration, and extracts user information from the payload to authorize the request.

This flow eliminates the need for server-side session storage, enabling stateless authentication that scales horizontally across multiple servers.

Decoding vs verifying JWTs

Decoding (reading the content)

Decoding a JWT extracts and Base64URL-decodes the header and payload without checking the signature. Anyone can decode a JWT—it's not encrypted, just encoded. Decoding is useful for debugging, inspecting token contents, or extracting claims for display purposes when you trust the source.

Use the JWT Decoder to inspect JWT contents without verification. This is safe for development and debugging but should never replace verification in production.

Verification (checking authenticity)

Verification decodes the JWT and validates the signature using the secret key or public key. If the signature matches, the token is authentic and hasn't been tampered with. If verification fails, reject the token—it's either forged or corrupted.

Always verify JWTs in production code. Accepting unverified JWTs allows attackers to forge tokens and impersonate users.

JWT security best practices

1. Use strong secret keys

Secret keys should be long (256+ bits), random, and kept secure. Weak or leaked keys allow attackers to forge valid JWTs. Use environment variables or secure key management systems (AWS KMS, HashiCorp Vault) to store keys—never hardcode them in source code.

2. Set short expiration times

JWTs should expire quickly to limit damage if stolen. Access tokens typically expire in 5-15 minutes. Use refresh tokens (longer-lived) to obtain new access tokens without requiring users to log in repeatedly. Store refresh tokens securely (httpOnly cookies or secure storage).

3. Don't store sensitive data in payloads

JWT payloads are Base64-encoded, not encrypted. Anyone who obtains the token can decode and read the payload. Store only non-sensitive identifiers like user IDs, roles, and permissions. Never include passwords, credit card numbers, or private keys.

4. Use HTTPS for transmission

JWTs transmitted over HTTP can be intercepted by attackers (man-in-the-middle attacks). Always use HTTPS to encrypt transmission and prevent token theft.

5. Implement token revocation when necessary

JWTs are stateless, making revocation difficult. If a user logs out or a token is compromised, it remains valid until expiration. For critical applications, maintain a token blacklist or use short-lived tokens with refresh mechanisms to minimize risk.

6. Validate all claims

Beyond signature verification, validate critical claims:

  • exp (expiration): Reject expired tokens.
  • iss (issuer): Verify the token comes from a trusted issuer.
  • aud (audience): Ensure the token is intended for your application.
  • nbf (not before): Reject tokens used before their valid time.

JWT signing algorithms

HMAC (HS256, HS384, HS512)

Symmetric signing using a shared secret key. Both the issuer and verifier use the same key. HMAC is simple and performant but requires securely sharing the secret key between parties. Suitable for internal systems where key distribution is manageable.

RSA (RS256, RS384, RS512)

Asymmetric signing using public/private key pairs. The issuer signs with the private key; verifiers use the public key. RSA allows distributing public keys without compromising security, ideal for microservices or third-party integrations where multiple parties need to verify tokens.

ECDSA (ES256, ES384, ES512)

Asymmetric signing using elliptic curve cryptography. ECDSA provides similar security to RSA with smaller key sizes and faster operations, making it efficient for mobile and IoT applications.

Common JWT use cases

1. Single Sign-On (SSO)

JWTs enable SSO across multiple applications. After authenticating with an identity provider (IdP), users receive a JWT that grants access to multiple services without re-authentication. The JWT contains user identity and permissions recognized by all services in the ecosystem.

2. API authentication

RESTful APIs use JWTs for stateless authentication. Clients include JWTs in Authorization headers, allowing servers to authenticate and authorize requests without database lookups. This scales well for high-traffic APIs.

3. Mobile app authentication

Mobile apps use JWTs to maintain authentication across app restarts and network interruptions. Tokens are stored securely on the device and included in API requests. Short-lived access tokens with secure refresh tokens provide a balance between security and user experience.

4. Microservices authorization

In microservices architectures, JWTs carry user identity and permissions between services. Each service verifies the JWT signature and extracts claims to authorize actions without querying a central authentication service, reducing latency and coupling.

JWT limitations and alternatives

Revocation challenges

Stateless JWTs can't be revoked before expiration. If a user logs out or a token is compromised, it remains valid until it expires. Solutions include maintaining a token blacklist (reintroducing state) or using very short expiration times with refresh tokens.

Size concerns

JWTs are larger than session IDs (typically 200-1000 bytes vs 20-30 bytes). Including JWTs in every request increases bandwidth. For applications with many requests per user session, this overhead can be significant.

Alternatives

  • Session tokens: Opaque identifiers that reference server-stored sessions. Easier to revoke but require server-side storage.
  • OAuth 2.0 tokens: Standardized token format for authorization. Often implemented using JWTs but can use other formats.
  • PASETO (Platform-Agnostic Security Tokens): Alternative to JWT addressing some security concerns with simpler, safer defaults.

Common JWT mistakes

1. Accepting unverified tokens

Decoding a JWT without verifying the signature allows attackers to forge tokens. Always verify signatures in production, even if the payload "looks" correct.

2. Using the "none" algorithm

Some JWT libraries support an "alg": "none" header for unsigned tokens. Attackers can modify tokens and set alg to "none" to bypass signature checks. Always validate the algorithm matches expected values and reject "none".

3. Exposing tokens in URLs

URLs are logged by browsers, servers, and proxies. Including JWTs in query parameters exposes them in logs. Use Authorization headers or secure cookies instead.

4. Not handling token expiration gracefully

Applications should detect expired tokens and prompt users to re-authenticate or use refresh tokens to obtain new access tokens. Failing silently or showing cryptic errors frustrates users.

FAQs

Is decoding a JWT the same as verifying it?
No. Decoding extracts the header and payload without checking authenticity. Verification confirms the signature matches, proving the token hasn't been tampered with and was signed by a trusted issuer. Always verify JWTs in production; decode only for debugging or inspecting trusted tokens.
Can I store sensitive data in JWTs?
No. JWT payloads are Base64-encoded, not encrypted—anyone can decode and read the contents. Never store passwords, credit cards, or private keys in JWTs. Store only non-sensitive identifiers and claims like user ID, roles, or expiration times.
How long should JWT expiration times be?
Access tokens should expire quickly (5-15 minutes) to limit damage if stolen. Refresh tokens can last longer (days to weeks) but should be securely stored and rotated. Balance security (short expiration) with user experience (avoid frequent re-authentication).
What is the difference between JWT and session tokens?
JWTs are stateless and self-contained—they carry all user information without server-side storage. Session tokens are opaque identifiers that reference server-stored session data. JWTs scale better but can't be revoked easily; session tokens require server storage but allow instant revocation.
Can JWTs be used for authentication and authorization?
Yes. Authentication (proving identity) happens when users log in and receive a JWT. Authorization (granting permissions) happens when the JWT's payload contains roles or permissions that servers check before allowing actions. JWTs handle both by embedding claims in the payload.

Next steps

To inspect and debug JWTs, use the JWT Decoder to decode the header and payload. For other encoding tasks, explore the Base64 Encoder/Decoder for Base64 operations related to JWT construction.