·10 min read·Encoding & Security

What is JWT? A Developer's Guide to JSON Web Tokens

Learn how JSON Web Tokens work, their three-part structure, common claims, security best practices, and when to use JWTs vs sessions for authentication.

Try our free JWT Decoder

Decode any JWT token instantly -- view header, payload, and expiry status.

Open Tool

What is a JSON Web Token?

A JSON Web Token (JWT, pronounced "jot") is an open standard (RFC 7519) for securely transmitting information between parties as a compact, URL-safe JSON object. JWTs are widely used for authentication and authorization in modern web applications, APIs, and microservices.

When a user logs in, the server creates a JWT containing the user's identity and permissions, signs it with a secret key, and sends it back to the client. The client then includes this token in subsequent requests (typically in the Authorizationheader). The server verifies the signature to confirm the token is genuine and hasn't been tampered with.

The key advantage of JWTs is that they are stateless -- the server does not need to store session data in a database or memory. All the information needed to authenticate the user is contained within the token itself. You can inspect any JWT using our JWT Decoder to see the header, payload, and expiration status.

The Three Parts of a JWT

A JWT consists of three parts separated by dots: header.payload.signature. Each part is Base64URL-encoded.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header

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

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

Common algorithms include HS256 (HMAC with SHA-256, symmetric key), RS256 (RSA with SHA-256, asymmetric key pair), and ES256 (ECDSA with SHA-256). RS256 is preferred for distributed systems because only the auth server needs the private key -- other services verify using the public key.

2. Payload

The payload contains the claims -- statements about the user and additional metadata:

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

The payload is only Base64URL-encoded, not encrypted. Anyone with the token can decode and read it. Never put sensitive data like passwords or credit card numbers in the payload.

3. Signature

The signature is created by combining the encoded header and payload, then signing them with the secret key:

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

The signature ensures integrity -- if anyone modifies the header or payload, the signature will no longer match, and the server will reject the token. You can verify this yourself by decoding a token with our JWT Decoder and checking its structure.

Common JWT Claims Explained

JWT defines a set of registered claims -- standardized fields with specific meanings. Here are the most important ones:

ClaimFull NameDescription
issIssuerWho issued the token (e.g., "https://auth.example.com")
subSubjectWho the token refers to (usually a user ID)
audAudienceWho the token is intended for (e.g., "https://api.example.com")
expExpiration TimeUnix timestamp when the token expires
iatIssued AtUnix timestamp when the token was created
nbfNot BeforeToken is not valid before this time
jtiJWT IDUnique identifier for the token (prevents replay attacks)

Here is how to create a JWT with these claims in JavaScript using the popular jsonwebtoken library:

import jwt from "jsonwebtoken";

const token = jwt.sign(
  {
    sub: "user_123",
    name: "Jane Smith",
    role: "editor",
    iss: "https://auth.myapp.com",
    aud: "https://api.myapp.com",
  },
  process.env.JWT_SECRET,
  { expiresIn: "1h" } // sets exp automatically
);

// Verify and decode
try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  console.log(decoded.sub);  // "user_123"
  console.log(decoded.role); // "editor"
} catch (err) {
  console.error("Invalid or expired token");
}

JWT vs Session-Based Authentication

Both JWTs and server-side sessions are valid approaches to authentication. The right choice depends on your architecture:

AspectJWTServer Sessions
StorageClient-side (token contains all data)Server-side (database or in-memory store)
ScalabilityExcellent -- no shared state between serversRequires shared session store (Redis, DB) across servers
RevocationDifficult -- token valid until expiry (needs blocklist)Easy -- delete session from server
SizeLarger -- payload sent with every requestSmaller -- only session ID sent
Cross-domainEasy -- send token in Authorization headerDifficult -- cookies are domain-scoped
Best forAPIs, microservices, mobile apps, SPAsTraditional server-rendered apps, apps needing instant revocation

Many production systems use a hybrid approach: JWTs for API authentication between services, and server-side sessions for user-facing web applications where instant revocation (e.g., logout, account ban) is important.

JWT Security Best Practices

  • Use short expiration times -- Set access tokens to expire in 15 minutes to 1 hour. Short-lived tokens limit the damage window if a token is stolen. Use refresh tokens (stored securely) to issue new access tokens.
  • Store tokens in httpOnly cookies -- HttpOnly cookies cannot be accessed by JavaScript, which protects against XSS attacks. Combine with the Secure and SameSite=Strict flags.
  • Avoid localStorage for tokens -- Any JavaScript on the page can read localStorage, including injected scripts from XSS vulnerabilities. This is the most common JWT security mistake in SPAs.
  • Always verify the signature -- Never trust a JWT without verifying its signature. Check the algorithm in the header matches what you expect -- do not accept alg: "none".
  • Validate all claims -- Check exp, iss, and aud on every request. An expired or misrouted token should be rejected immediately.
  • Use strong secrets -- For HMAC algorithms (HS256), use a secret that is at least 256 bits of entropy. For RSA (RS256), use 2048-bit keys minimum. Generate secrets with our Hash Generator.
  • Implement token rotation -- Rotate refresh tokens on each use. If a refresh token is used twice, it indicates a potential theft -- invalidate all tokens for that user.

Common JWT Mistakes

These are the most frequent security and implementation errors developers make with JWTs:

1. Storing Sensitive Data in the Payload

Bad
{"password": "secret123", "ssn": "123-45-6789"}
Good
{"sub": "user_123", "role": "admin"}

The payload is only Base64-encoded, not encrypted. Anyone can decode it. Only store identifiers and non-sensitive metadata.

2. Not Validating the Algorithm

Bad
// Accepting any algorithm jwt.verify(token, secret)
Good
// Specifying allowed algorithms jwt.verify(token, secret, { algorithms: ["HS256"] })

Attackers can change the algorithm to 'none' or switch from RS256 to HS256 (using the public key as the HMAC secret). Always specify the expected algorithm.

3. Using Long-Lived Access Tokens

Bad
{ expiresIn: "30d" } // 30-day access token
Good
{ expiresIn: "15m" } // 15-minute access token + refresh token

Long-lived access tokens give attackers a wide window to exploit stolen tokens. Use short-lived access tokens with a refresh token pattern.

4. No Token Revocation Strategy

Bad
// No way to invalidate a token before expiry
Good
// Maintain a blocklist of revoked token IDs (jti)

JWTs cannot be revoked by default. For logout and security events, maintain a blocklist of revoked token IDs, or use short expiry times so compromised tokens expire quickly.

Here is a complete example of the refresh token pattern in an Express.js API:

import jwt from "jsonwebtoken";

// Login endpoint — issues both tokens
app.post("/login", async (req, res) => {
  const user = await authenticateUser(req.body);

  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.ACCESS_SECRET,
    { expiresIn: "15m" }
  );

  const refreshToken = jwt.sign(
    { sub: user.id },
    process.env.REFRESH_SECRET,
    { expiresIn: "7d" }
  );

  // Store refresh token in httpOnly cookie
  res.cookie("refreshToken", refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  });

  res.json({ accessToken });
});

// Refresh endpoint — issues new access token
app.post("/refresh", (req, res) => {
  const { refreshToken } = req.cookies;
  if (!refreshToken) return res.status(401).json({ error: "No refresh token" });

  try {
    const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET, {
      algorithms: ["HS256"],
    });

    const newAccessToken = jwt.sign(
      { sub: payload.sub, role: payload.role },
      process.env.ACCESS_SECRET,
      { expiresIn: "15m" }
    );

    res.json({ accessToken: newAccessToken });
  } catch {
    res.status(403).json({ error: "Invalid refresh token" });
  }
});

Frequently Asked Questions

Can anyone read a JWT token?
Yes. The header and payload are only Base64URL-encoded, not encrypted. Anyone with the token can decode and read its contents using a tool like our JWT Decoder or a simple Base64 decode. The signature prevents tampering but does not hide the data. Never store passwords, credit card numbers, or other secrets in a JWT. If you need encrypted tokens, use JWE (JSON Web Encryption).
How do I check if a JWT is expired?
Decode the payload (it's just Base64URL) and check the 'exp' claim. The exp value is a Unix timestamp in seconds. In JavaScript: const isExpired = Date.now() / 1000 > decodedPayload.exp. Most JWT libraries like jsonwebtoken automatically reject expired tokens during verification, throwing a TokenExpiredError.
Should I store JWT in localStorage or cookies?
HttpOnly cookies are more secure because JavaScript cannot access them, protecting against XSS attacks. The tradeoff is that cookies require CSRF protection (use SameSite=Strict) and are scoped to a domain. localStorage is accessible to any JavaScript on the page, including malicious scripts injected through XSS vulnerabilities. For most web apps, httpOnly cookies are the recommended choice.
What is the difference between access tokens and refresh tokens?
Access tokens are short-lived (15 minutes to 1 hour) and sent with every API request to authenticate the user. Refresh tokens are long-lived (days to weeks) and stored securely (httpOnly cookie). When the access token expires, the client uses the refresh token to get a new access token without requiring the user to log in again. This limits the damage if an access token is stolen while maintaining a smooth user experience.

Decode Your JWT Tokens Now

Paste any JWT token to instantly view the header, payload, claims, and expiration status. Free, private, runs in your browser.

Open JWT Decoder

Related Tools