Guide · 6 min read

How to create a JSON Web Token

A JWT is just three base64url-encoded strings joined by dots. Here is what each segment is, how the signature is produced, and how to mint one in five popular runtimes.

Anatomy

Three segments, joined by dots

A JWT looks like xxx.yyy.zzz — a header, a payload, and a signature, each base64url-encoded. The signature covers the encoded header and payload, so tampering with either invalidates the token.

Header
eyJhbGciOi…
.
Payload
eyJzdWIiOi…
.
Signature
zHsr2tQ5…
  1. Build the payload (claims)

    The payload carries the claims. Use registered claims for things consumers already understand, and add custom claims for your application.

    {
      "iss": "https://auth.acme.com",
      "sub": "usr_01HXYZ",
      "aud": "api.acme.com",
      "iat": 1716500000,
      "exp": 1716503600,        // 1 hour later
      "scope": "read:docs write:docs"
    }

    Base64url-encode this JSON to produce the second segment.

    Don't put secrets in the payload. It is encoded, not encrypted — anyone who has the token can read every claim.

  2. Sign the encoded header + payload

    Concatenate the two encoded segments with a dot, then run the signing algorithm over that string. Base64url-encode the result to produce the third segment.

    // pseudocode
    signingInput = base64url(headerJson) + "." + base64url(payloadJson)
    signature    = HMAC_SHA256(signingInput, secret)        // HS256
                  // or RSA_SHA256(signingInput, privateKey)    // RS256
    
    jwt = signingInput + "." + base64url(signature)

    That's it — you have a JWT. In practice, never build this from scratch. Use a vetted library so algorithm handling, key parsing, and constant-time comparison are correct.

Examples

Code examples

All examples sign the same payload with HS256, expiring in 1 hour. See Libraries for the rest.

// npm install jsonwebtoken
import jwt from "jsonwebtoken";

const token = jwt.sign(
  {
    sub: "usr_01HXYZ",
    scope: "read:docs",
  },  
  process.env.JWT_SECRET,
  {
    algorithm: "HS256",
    issuer:    "https://auth.acme.com",
    audience:  "api.acme.com",
    expiresIn: "1h",
  }
);
On receipt

Verifying a token

Decoding is not verifying. Anyone can decode any JWT. Before trusting a token you must:

  • Validate the signature with the expected algorithm and key — never accept alg from the token alone.
  • Check exp and nbf against the current clock (allow a small ±60s skew).
  • Check iss and aud match what your service expects.
  • Reject anything you don't recognise — unknown alg, missing claims, oversized tokens.

See Best Practices and Security for the full checklist.