JWT Tokens Explained: Structure, Security, and Best Practices

· 10 min read

JWT Structure

A JWT consists of three Base64URL-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
|---- Header ----|.|----- Payload ------|.|----- Signature -----|
PartContainsExample (decoded)
HeaderAlgorithm + token type{"alg": "HS256", "typ": "JWT"}
PayloadClaims (user data){"sub": "1234567890", "name": "John", "iat": 1516239022}
SignatureVerification hashHMAC-SHA256(header + "." + payload, secret)

Decode any JWT with our JWT Decoder or generate test tokens with JWT Generator.

Standard Claims (RFC 7519)

ClaimNamePurpose
issIssuerWho created the token
subSubjectWho the token is about (user ID)
audAudienceWho the token is intended for
expExpirationWhen the token expires (Unix timestamp)
nbfNot BeforeToken is not valid before this time
iatIssued AtWhen the token was created
jtiJWT IDUnique identifier (for revocation)

Authentication Flow

  1. User sends credentials (username + password) to login endpoint
  2. Server verifies credentials, creates JWT with user claims, signs it
  3. Server returns JWT to client
  4. Client stores JWT (memory, httpOnly cookie, or localStorage)
  5. Client sends JWT in Authorization header: Bearer eyJhbG...
  6. Server verifies signature and extracts claims — no database lookup needed
// Node.js example with jsonwebtoken
const jwt = require('jsonwebtoken');

// Create token
const token = jwt.sign(
  { userId: 123, role: 'admin' },
  process.env.JWT_SECRET,
  { expiresIn: '15m' }
);

// Verify token
try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  console.log(decoded.userId); // 123
} catch (err) {
  // Token invalid or expired
}

Security Best Practices

DoDon't
Use short expiration (15 min)Set tokens to never expire
Store in httpOnly cookiesStore in localStorage (XSS vulnerable)
Use RS256 for distributed systemsUse HS256 with weak secrets
Validate all claims (iss, aud, exp)Only check the signature
Use a strong secret (256+ bits)Use "secret" or short passwords
Implement token revocationAssume tokens can't be invalidated

Common attacks:

Refresh Tokens

Access tokens should be short-lived (15 min). Refresh tokens are long-lived (7-30 days) and used to get new access tokens without re-authentication:

  1. Login returns access token (15 min) + refresh token (7 days)
  2. Access token expires → client sends refresh token to /refresh endpoint
  3. Server validates refresh token, issues new access token
  4. Refresh token is rotated (old one invalidated, new one issued)

Refresh tokens should be stored server-side (database) so they can be revoked. Access tokens are stateless (no server storage needed).

Frequently Asked Questions

Is JWT encrypted?

Standard JWT (JWS) is signed but NOT encrypted. Anyone can decode the payload. Never put sensitive data (passwords, SSN) in a JWT. Use JWE (JSON Web Encryption) if you need encrypted tokens.

JWT vs session cookies?

JWTs are stateless (no server storage), work across domains, and scale horizontally. Sessions require server-side storage but are easier to revoke. Use sessions for traditional web apps, JWTs for APIs and microservices.

How do I revoke a JWT?

JWTs are stateless, so you can't directly revoke them. Options: short expiration + refresh tokens, token blacklist (Redis), or change the signing key (invalidates ALL tokens).

Should I use HS256 or RS256?

HS256 (symmetric) is simpler — one shared secret. RS256 (asymmetric) uses public/private keys — better for distributed systems where multiple services verify tokens but only one signs them.

Related Tools

JWT Decoder JWT Generator Hash Generator Base64