JWT Signing vs. Verifying: What Actually Proves What
What a JWT signature does and doesn't prove, why HS256 and RS256 need completely different trust models, and the mistakes that undermine both.
Published March 5, 2026
A JWT's payload is not encrypted, and that surprises people the first time they decode one and see their own data sitting there in plain, readable JSON. The signature isn't there to hide the contents. It's there to prove the contents haven't been tampered with since the token was issued. Those are two different jobs, and conflating them is where a lot of JWT mistakes start.
What the signature actually proves
Signing takes the token's header and payload, runs them through a cryptographic function along with a key, and produces a signature that only someone holding the correct key could have generated. Verifying repeats that same calculation and checks whether the result matches the signature attached to the token. If even one character in the payload changes, the signature no longer matches, and verification fails.
That's the entire guarantee: this exact payload was signed by someone holding the key, and hasn't been altered since. It says nothing about who can read the payload, since anyone can decode the Base64 and see it, and it says nothing about whether the payload's claims are still valid right now (that's what an exp expiration claim and the verifying server's own checks are for).
Why HS256 and RS256 aren't interchangeable
HS256 uses one shared secret for both signing and verifying. Whoever has that secret can do both jobs, which means it only works when signing and verifying happen on systems that already trust each other completely, like a backend service signing a token that the same backend later verifies.
RS256 uses a private key to sign and a separate public key to verify. The private key stays secret with whoever issues tokens; the public key can be handed out freely to anyone who needs to verify a token without being trusted to issue new ones. That asymmetry is exactly why RS256 is the right choice when multiple independent services need to verify tokens they didn't issue themselves, and exactly why it's not something to generate casually in a browser tool: the private key needs real protection.
The mistake that undermines both
Skipping verification on the receiving end. A shocking amount of real-world JWT code decodes a token, reads the claims, and trusts them, without ever calling the actual verify function that checks the signature. A decoded-but-unverified JWT is just a piece of client-suppliable JSON at that point; anyone can craft one with any claims they want. The signature only protects you if something actually checks it before the payload gets trusted.