How to Create and Sign a JWT

By Ramanathan Oct 9, 2026 4 min read JWT Decoder & Encoder

Decoding a JWT shows you what's inside one. Creating one is the other half: you choose how it will be signed, decide which claims it carries, and sign it with a key the receiver can check. Here's the whole process, and the mistakes that make a token unsafe.

What signing actually does

A signed JWT (strictly, a JWS) is three Base64URL segments joined by dots. The first two are your header and payload. The third is a signature computed over exactly this string:

base64url(header) + "." + base64url(payload)

Change a single character of the header or payload afterwards and the signature no longer matches, so the receiver rejects the token. Signing doesn't hide the contents, though. The payload is only encoded, so anyone can read it (see Base64 Is Not Encryption).

Step 1: choose an algorithm

The alg header decides what kind of key you need:

FamilyAlgorithmsSigns withVerifies with
HMACHS256, HS384, HS512Shared secretThe same secret
RSARS256, RS384, RS512RSA private keyRSA public key
RSA-PSSPS256, PS384, PS512RSA private keyRSA public key
ECDSAES256, ES384, ES512EC private keyEC public key
EdDSAEdDSA (Ed25519)Ed25519 private keyEd25519 public key

Use HMAC when one service both issues and checks the token: it's simple and fast, but everyone who can verify can also forge. Use an asymmetric algorithm (ES256 and EdDSA give short keys and signatures; RS256 is the most widely supported) when other services or third parties must verify. They only ever get the public key.

Step 2: write the header

The header is a small JSON object. alg is required, and typ is conventionally JWT:

{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "2026-10-signing-key"
}

Add a kid (key ID) if you rotate keys or publish several in a JWK Set. The verifier uses it to pick the right public key.

Step 3: choose the claims

The payload holds claims. The registered ones have fixed meanings:

  • iss: who issued the token, e.g. https://auth.example.com.
  • sub: who it's about, usually a user ID.
  • aud: who it's for. Receivers should reject tokens meant for someone else.
  • exp: expiry, as a Unix timestamp in seconds.
  • iat / nbf: issued-at and not-before times.
  • jti: a unique ID, useful for revocation lists or replay checks.
{
  "iss": "https://auth.example.com",
  "sub": "user_8412",
  "aud": "orders-api",
  "iat": 1791561600,
  "exp": 1791565200,
  "scope": "orders:read"
}

Keep the payload small and never put secrets in it. Passwords, API keys and personal data you wouldn't print in a log all stay out.

Step 4: sign it

For HMAC, the key is a secret byte string. It should be at least as long as the hash: 32 random bytes for HS256, 48 for HS384 and 64 for HS512. A short, guessable secret such as secret can be brute-forced offline from a single token.

For the asymmetric families, you sign with the private key, usually a PEM block (-----BEGIN PRIVATE KEY-----) or a private JWK, and hand out only the public key. ECDSA signatures in a JWT are the raw r || s values, not the DER structure many crypto libraries produce by default. That's a classic source of "invalid signature" errors when mixing libraries.

In code, use a maintained library rather than assembling tokens by hand. For example, with jose in Node.js:

import { SignJWT, importPKCS8 } from "jose";

const key = await importPKCS8(process.env.JWT_PRIVATE_KEY, "ES256");
const token = await new SignJWT({ scope: "orders:read" })
  .setProtectedHeader({ alg: "ES256", kid: "2026-10-signing-key" })
  .setIssuer("https://auth.example.com")
  .setSubject("user_8412")
  .setAudience("orders-api")
  .setIssuedAt()
  .setExpirationTime("1h")
  .sign(key);

Step 5: verify what you made

Always check a new token the way its receiver will: decode it, confirm the claims, and verify the signature with the public key (or the secret). The receiver should also pin the expected algorithm, check exp, nbf, iss and aud, and reject anything else. How JWT Authentication Works covers that side of the exchange.

Mistakes that make tokens unsafe

  • alg: none. An unsigned token proves nothing. Never issue one, and configure verifiers to reject it.
  • Algorithm confusion. A verifier that trusts the token's alg can be tricked into checking an RS256 token as HS256, using the public key as the HMAC secret. Pin the algorithm on the verifying side.
  • Weak HMAC secrets. Generate them randomly. Don't use a word or a password.
  • No expiry, or a very long one. Stateless tokens can't easily be revoked, so keep exp short and use refresh tokens for long sessions.
  • Production keys in tools. Use throwaway test keys when experimenting, even in a tool that runs locally.

Related

To read an existing token, see How to Decode a JWT. For the wider picture, see Web Security Essentials and Encoding vs Encryption vs Hashing.

Try it

Open the JWT tool's Encoder tab, pick an algorithm, and click Generate for a test secret or key pair. Edit the header and payload, add iat or exp with one click, and copy the signed token. Decode & verify it then checks the signature with the matching key. Everything runs in your browser, so tokens and keys are never uploaded.

About the author

Ramanathan · Software Engineer & Solutions Architect

I'm a Software Engineer and Solutions Architect with 20+ years of experience building enterprise applications across BFSI, Healthcare, Retail, Manufacturing, and Industrial Automation. I've spent those two decades living in JSON, tokens, regexes, and config files — so I built the fast, private, no-login developer tools I always wanted to reach for myself.

Last updated: Oct 9, 2026