devkult_
tools24converters26
home/blog/how-jwt-signing-works
securityjwtauth

How JWT Signing Works (and Why You Can't Verify It in the Browser)

A JSON Web Token looks like three chunks of gibberish separated by dots. The first two chunks aren't secret at all — they're just Base64URL-encoded JSON. The third chunk, the signature, is the only part that matters for trust. Understanding how it's produced explains a lot of JWT behaviour, including why you can decode a token in the browser but never truly verify it there.

The three parts

eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMifQ . 4Vj...signature
└── header ──────────┘ └── payload ──────┘ └── signature
  • Header — JSON describing the algorithm, e.g. {"alg":"HS256","typ":"JWT"}
  • Payload — JSON of claims, e.g. {"sub":"123","exp":1736695422}
  • Signature — a cryptographic value computed over the first two parts

You can decode the header and payload yourself in the JWT decoder — they're just Base64URL. The interesting question is how the signature is made.

HS256: a shared secret

HS256 means "HMAC with SHA-256." The signature is:

HMAC_SHA256( base64url(header) + "." + base64url(payload), secret )

The server takes the header and payload, runs them through HMAC-SHA256 using a secret key only it knows, and attaches the result. When a token comes back, the server recomputes the HMAC with the same secret and checks that it matches the signature on the token. If even one byte of the payload changed, the recomputed value won't match, and the token is rejected.

The security rests entirely on the secret staying secret. Anyone who has it can forge valid tokens.

RS256: a key pair

RS256 uses RSA. The server signs with a private key and hands out a public key that anyone can use to verify. This is better for distributed systems: many services can verify tokens using the public key without ever being able to mint new ones. Only the holder of the private key can sign.

The verification math differs, but the principle is the same: the signature proves the payload was produced by someone holding the key and hasn't been altered since.

Why you can't verify a JWT in the browser

This is the part people get wrong. To verify an HS256 token you need the secret. To verify an RS256 token you need the public key — and, ideally, to check the issuer, audience, and expiry against values your backend trusts.

A browser tool can:

  • split the token on the dots,
  • Base64URL-decode the header and payload,
  • show you the claims and convert iat/exp timestamps to readable dates.

A browser tool cannot safely verify the signature, because:

  1. For HS256, putting the secret in client-side code exposes it to every visitor — which would let them forge tokens.
  2. Verification is only meaningful against trusted issuer/audience values, which live on the server.

So the JWT decoder deliberately stops at decoding. It runs entirely in your browser, never sends the token anywhere, and shows you what's inside — but the real verification belongs on the server that holds the key.

Practical takeaways

  • The header and payload are not confidential. Anyone can read them. Don't store secrets there.
  • exp is enforced by the verifier, not by the token. A decoded token can look valid while being expired — always check the expiry on the server.
  • Rotate HS256 secrets and RS256 private keys like any other credential; a leaked signing key means forged tokens.

Decoding a token tells you what it claims. Only the server with the key can tell you whether to believe it.

More from the blog