Skip to main content
ByteKiwi

JWT Tokens Explained: Structure, Claims, and Security

A practical breakdown of JSON Web Tokens - how the three-part structure works, what claims mean, how signatures are verified, and when to use JWTs instead of sessions.

6 min read
JWT Auth Security JSON

A JSON Web Token (JWT) is a compact, URL-safe string used to securely transmit information between two parties. You’ll find them in authentication headers, OAuth flows, and API authorization checks.

Before you decode your next token, it helps to know what’s actually inside one - and why each part exists.

What a JWT looks like

A JWT is three Base64url-encoded segments separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Split it at the dots and you get:

  1. Header - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  2. Payload - eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE2MjM5MDIyfQ
  3. Signature - SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

You can paste any JWT into the ByteKiwi JWT Decoder to inspect all three parts without writing a line of code.

Part 1: Header

The header identifies the token type and the signing algorithm.

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

Common algorithm values:

AlgorithmTypeNotes
HS256HMAC-SHA256Symmetric - same secret signs and verifies
RS256RSA-SHA256Asymmetric - private key signs, public key verifies
ES256ECDSA-SHA256Asymmetric - smaller signatures than RS256
noneNo signatureNever use in production

Part 2: Payload

The payload contains claims - statements about the user or session. There are three claim types.

Registered claims

These are standardized claim names defined by RFC 7519:

{
  "iss": "https://auth.example.com",
  "sub": "user_abc123",
  "aud": "https://api.example.com",
  "exp": 1716239022,
  "iat": 1716235422,
  "nbf": 1716235422,
  "jti": "a1b2c3d4"
}
ClaimFull nameMeaning
issIssuerWho created the token
subSubjectWho the token is about (usually a user ID)
audAudienceWho should accept the token
expExpirationUnix timestamp after which the token is invalid
iatIssued atWhen the token was created
nbfNot beforeEarliest time the token is valid
jtiJWT IDUnique token identifier (for revocation)

Public claims

Collision-resistant names registered in the IANA JWT Claims Registry, like name, email, and picture from OpenID Connect.

Private claims

Custom claims your application defines:

{
  "sub": "user_abc123",
  "roles": ["admin", "editor"],
  "org_id": "org_xyz",
  "plan": "pro"
}

Part 3: Signature

The signature proves the token hasn’t been tampered with. For HS256, it’s computed as:

HMAC-SHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

For RS256, a private key signs the data and anyone with the corresponding public key can verify it. This is how Auth0, Okta, and most SSO providers work - they publish their public keys at a well-known URL so your server can verify tokens without ever seeing the private key.

How JWT authentication works

A typical flow:

  1. User logs in with username and password
  2. Server validates credentials, creates a JWT signed with its secret/private key
  3. Server sends the JWT to the client (usually in the response body)
  4. Client stores the token (memory, localStorage, or a secure httpOnly cookie)
  5. Client sends the token in the Authorization header for protected requests:
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  6. Server verifies the signature and checks exp, then grants or denies access

JWT vs session tokens

JWTSession token
StorageClient holds the dataServer stores state in a DB
RevocationHard (must wait for expiry)Easy (delete from DB)
ScalabilityStateless - works across multiple serversSession store must be shared
SizeLarger (full payload in each request)Small (just an ID)
Use caseMicroservices, APIs, mobileTraditional web apps

Common JWT security mistakes

1. Storing tokens in localStorage LocalStorage is accessible to JavaScript and vulnerable to XSS attacks. Prefer httpOnly cookies for refresh tokens.

2. Not validating the aud claim If your server accepts tokens issued for a different service, an attacker can reuse tokens across your APIs.

3. Using weak secrets for HS256 A short or guessable secret can be brute-forced offline. Use at least 256 bits of entropy (a long random string).

4. Ignoring the exp claim Some JWT libraries won’t auto-reject expired tokens. Always check that exp > current_time in your verification logic.

5. Trusting the algorithm from the header Specify the expected algorithm on the server - never let the token header dictate which algorithm to use.

Decoding a JWT

To inspect a JWT in your browser:

// Decode without verification (for debugging only)
function decodeJWT(token) {
  const [, payload] = token.split('.');
  const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
  return JSON.parse(json);
}

decodeJWT('eyJhbGciOiJIUzI1NiJ9...');
// { sub: "user_abc123", roles: ["admin"], exp: 1716239022 }

Or use the JWT Decoder tool - it handles Base64url padding and shows the header, payload, and expiry status in one view.

FAQ

What is the difference between JWT and OAuth?

OAuth is an authorization framework that defines how to delegate access. JWT is a token format often used inside OAuth flows. OAuth 2.0 doesn’t require JWTs - you can use opaque tokens - but JWTs are common because they’re self-contained.

Can I invalidate a JWT before it expires?

Not directly - that’s one of JWT’s tradeoffs. Common workarounds include: keeping a blocklist of revoked jti values, using very short expiry times, or switching to opaque tokens with a database-backed session store for cases where immediate revocation matters.

How do I verify a JWT in Node.js?

import jwt from 'jsonwebtoken';

const payload = jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ['HS256'],
  audience: 'https://api.example.com',
});

Always pass the expected algorithms array - never use the default, which can be manipulated.

Is Base64url the same as Base64?

Almost. Base64url replaces + with - and / with _, and omits padding (=). This makes it safe to embed in URLs and HTTP headers without percent-encoding.

What should I put in a JWT payload?

Only non-sensitive, frequently-needed data: user ID, roles, and session metadata. Avoid PII unless the token is encrypted (JWE). Keep payloads small - they travel in every API request header.