Guide2026-03-1110 min read

JWT Explained: How JSON Web Tokens Work Under the Hood

What is a JWT?

A JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting information between parties as a compact, URL-safe JSON object. JWTs are digitally signed, which means they can be verified and trusted. They are the most widely used token format for authentication and authorization in modern web applications.

Unlike traditional session-based authentication, where the server must store session data in memory or a database, JWTs are stateless. All the information needed to verify the user is contained within the token itself. This makes JWTs ideal for distributed systems, microservices, and single-page applications.

JWT Structure: Three Parts

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

xxxxx.yyyyy.zzzzz
|       |       |
Header  Payload Signature

Each part is Base64URL-encoded, making the token safe to transmit in URLs, HTTP headers, and cookies.

1. Header

The header typically contains two fields: the signing algorithm (alg) and the token type (typ).

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

This JSON is Base64URL-encoded to form the first part of the JWT. Common algorithms include HS256 (HMAC with SHA-256) and RS256 (RSA with SHA-256).

2. Payload

The payload contains the claims — statements about the user and additional metadata. Claims are the actual data carried by the token.

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "john@example.com",
  "role": "admin",
  "iat": 1735689600,
  "exp": 1735776000
}

There are three types of claims:

  • Registered claims: Standardized, recommended claims defined in the JWT spec (see below)
  • Public claims: Custom claims that should be defined in the IANA JSON Web Token Claims registry to avoid collisions
  • Private claims: Custom claims agreed upon between the parties (e.g., role, department)

3. Signature

The signature ensures that the token has not been tampered with. It is created by taking the encoded header, the encoded payload, a secret key, and the algorithm specified in the header:

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

When a server receives a JWT, it recalculates the signature using the same secret. If the calculated signature matches the one in the token, the token is valid and has not been modified.

Standard Claims Explained

The JWT specification defines several registered claim names. While none are mandatory, they are widely used and recommended:

  • iss (Issuer): Identifies who issued the token (e.g., "https://auth.example.com")
  • sub (Subject): Identifies the subject of the token, usually the user ID
  • aud (Audience): Identifies the intended recipient of the token (e.g., "https://api.example.com")
  • exp (Expiration Time): A Unix timestamp after which the token is no longer valid
  • nbf (Not Before): A Unix timestamp before which the token should not be accepted
  • iat (Issued At): A Unix timestamp indicating when the token was created
  • jti (JWT ID): A unique identifier for the token, useful for preventing replay attacks

How JWT Authentication Works

Here is the typical flow for JWT-based authentication in a web application:

  1. Login: The user submits their credentials (username and password) to the authentication server
  2. Token generation: The server verifies the credentials, creates a JWT containing the user's claims, signs it with a secret key, and returns it to the client
  3. Token storage: The client stores the JWT (typically in an HttpOnly cookie or in memory)
  4. Authenticated requests: For every subsequent API call, the client includes the JWT in the Authorization header as a Bearer token
  5. Token verification: The API server verifies the JWT's signature and checks the expiration claim before processing the request
// Client sends:
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// Server verifies the token and responds:
{
  "id": 1,
  "name": "John Doe",
  "email": "john@example.com"
}

Signing Algorithms: HS256 vs RS256

The two most common signing algorithms serve different use cases:

HS256 (HMAC + SHA-256)

A symmetric algorithm that uses the same secret key for both signing and verification. It is simpler to implement and faster to compute.

  • Best for: Single-server applications or microservices where all services share the same secret
  • Trade-off: Every service that needs to verify tokens must have access to the secret key, which increases the attack surface

RS256 (RSA + SHA-256)

An asymmetric algorithm that uses a private key to sign and a public key to verify. Only the authentication server holds the private key; all other services use the public key.

  • Best for: Distributed systems, third-party integrations, and architectures where you do not want to share signing secrets
  • Trade-off: Slower to compute and requires managing a key pair

Security Best Practices

1. Always Set an Expiration

Never issue JWTs without an exp claim. Short-lived tokens (15 minutes to 1 hour) limit the window of opportunity if a token is compromised. Use refresh tokens for long-lived sessions.

2. Never Store Sensitive Data in the Payload

JWT payloads are Base64URL-encoded, not encrypted. Anyone who intercepts a token can decode the payload and read its contents. Never include passwords, credit card numbers, or other sensitive information in a JWT.

// NEVER do this:
{
  "sub": "user123",
  "password": "s3cret!",
  "ssn": "123-45-6789"
}

// Instead, only include what's necessary:
{
  "sub": "user123",
  "role": "admin",
  "exp": 1735776000
}

3. Use Strong Secrets

For HS256, the secret key should be at least 256 bits (32 bytes) of cryptographically random data. Avoid using short, predictable strings like "secret" or "password123".

4. Validate All Claims

When verifying a JWT, check not only the signature but also:

  • exp: Reject expired tokens
  • iss: Verify the issuer matches your auth server
  • aud: Verify the audience matches your application
  • nbf: Reject tokens used before their valid period

5. Use HTTPS Only

Always transmit JWTs over HTTPS. Sending tokens over plain HTTP exposes them to man-in-the-middle attacks.

6. Consider Token Storage Carefully

  • HttpOnly cookies: Protected from XSS attacks but vulnerable to CSRF (mitigate with CSRF tokens)
  • localStorage: Accessible via JavaScript, making it vulnerable to XSS attacks
  • In-memory: Most secure option but tokens are lost on page refresh

Common Mistakes

  • Using alg: "none": Some libraries allow unsigned JWTs. Always reject tokens with the none algorithm in production.
  • Not checking the algorithm: An attacker might change the algorithm from RS256 to HS256 and sign with the public key. Always enforce expected algorithms on the server side.
  • Using JWTs for sessions: JWTs cannot be invalidated server-side without maintaining a blocklist, which defeats the purpose of being stateless. For session management requiring immediate revocation, consider traditional server-side sessions.
  • Token size bloat: Adding too many claims makes the JWT large, increasing bandwidth usage on every request. Keep payloads minimal.

Conclusion

JWTs are a powerful and flexible mechanism for authentication and authorization in modern web applications. Their stateless nature makes them ideal for distributed architectures, and their standardized structure ensures interoperability across platforms and languages.

However, JWTs are not a silver bullet. Understanding the structure, choosing the right signing algorithm, setting proper expiration times, and following security best practices are all essential to using JWTs safely and effectively.

JWT Decoder — Decode JWT tokens, inspect headers and payloads, and verify signatures right in your browser. No installation required.

JWT Decoder

Decode and verify JWT tokens in your browser

Try it now

Related Posts