JWT signing explained

Signing turns a set of claims into a token that anyone can read but only the key holder can create. Here is exactly what happens, what it protects — and what it doesn't.

What signing does, step by step

  1. Serialize the header, e.g. {"alg":"RS256","typ":"JWT","kid":"2026-09"}, and Base64URL-encode it.
  2. Serialize the claims and Base64URL-encode them.
  3. Join them with a dot to form the signing input.
  4. Compute the signature over the signing input with the private key or secret, using exactly the algorithm in the header.
  5. Append a dot and the Base64URL-encoded signature.
input     = b64url(header) + "." + b64url(payload)
signature = RSASSA-PKCS1-v1_5-SIGN(privateKey, SHA-256(input))   // RS256
token     = input + "." + b64url(signature)

Signing is not encryption

The header and payload are encoded, not encrypted. Anyone holding the token can read the claims — paste any token into JWTDecoder to see for yourself. Never put passwords, secrets or sensitive personal data in a signed JWT. If the claims must be confidential, encrypt the token (JWE) or keep the data server-side and reference it by ID.

Symmetric vs asymmetric keys

With HMAC (HS*), the same secret signs and verifies. It is simple and fast, but every verifier can also forge tokens. With RSA or ECDSA (RS*, PS*, ES*), only the private key can sign; verifiers use the public key, which can be published openly as a JWKS. Choose asymmetric keys when more than one service verifies your tokens.

Self-verification

JWTEncoder verifies every token it signs before you copy it — with the same secret for HMAC, or with the public key derived from your private key. A mismatch between the algorithm, key type and curve is reported with a specific explanation rather than a generic “invalid key”.

Build claims, choose an algorithm and sign — then copy the token, a Bearer header or a cURL flag.

Sign a token

See also: HS256 · RS256