How to Verify JWT Signatures: HS256, RS256, and ES256
How to Verify JWT Signatures
A JWT carries its claims in plaintext. The only thing preventing forgery is the signature. Without verification, an attacker can craft arbitrary tokens and impersonate any user. This guide covers the three most common JWT signing algorithms and how to verify each one.
The Verification Process
Regardless of the algorithm, JWT signature verification follows the same steps:
- Split the token on
.to extract header, payload, and signature. - Base64url-decode the header to read the
algfield. - Recompute the signature from header and payload using the algorithm and key.
- Compare the recomputed signature with the token's signature.
- If they match, the token is authentic.
HS256 (HMAC-SHA256)
HS256 is a symmetric algorithm — the same secret key creates and verifies the signature.
signature = HMACSHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
secret
)
To verify, recompute the HMAC with the shared secret and compare:
Token: header.payload.sig
Secret: "my-shared-secret"
1. Compute: HMACSHA256("header.payload", "my-shared-secret")
2. Compare: computedSignature === sig
Security considerations: The secret must be confidential, rotated periodically, and have at least 256 bits of entropy. HS256 is not ideal for microservice architectures where many services need the shared secret.
RS256 (RSA-SHA256)
RS256 is asymmetric. The issuer signs with a private key; verifiers check with the corresponding public key.
signature = RSASHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
privateKey
)
To verify, use the public key:
Token: header.payload.sig
Public: "-----BEGIN PUBLIC KEY-----..."
1. Load the RSA public key
2. Verify: RSA_SHA256_verify("header.payload", sig, publicKey)
Advantages: The signing and verification keys are separate. Any service can verify without holding a secret. Public keys can be served from a JWKS endpoint for easy rotation.
ES256 (ECDSA-SHA256)
ES256 uses ECDSA with the P-256 curve. Like RS256, it is asymmetric.
signature = ECDSA_SHA256(
base64urlEncode(header) + "." + base64urlEncode(payload),
privateKey
)
Verification uses the ECDSA P-256 public key. ES256 produces much smaller signatures (64 bytes vs. 256 bytes for RSA-2048) and offers faster verification.
Algorithm Comparison
| Property | HS256 | RS256 | ES256 | |----------|-------|-------|-------| | Key type | Symmetric (shared secret) | Asymmetric (RSA pair) | Asymmetric (EC pair) | | Signature size | 32 bytes | 256 bytes | 64 bytes | | Key distribution | Shared secretly | Public key is public | Public key is public | | Rotation | Moderate | Easy (JWKS) | Easy (JWKS) | | Use case | Trusted internal systems | Distributed APIs | Mobile, IoT |
Common Verification Pitfalls
- Algorithm confusion attack. If your code reads
algfrom the token without enforcement, an attacker can change the algorithm. Always validate that the algorithm matches the expected value. - Accepting
alg: "none". Some libraries accept unsigned tokens. Rejectalg: "none"explicitly. - Signature truncation. Non-constant-time comparison can leak information via timing attacks. Use your JWT library's built-in comparison.
Verify JWT Tokens Online
Use the JWT decoder tool to decode and validate any token. Paste the token, view the header and payload, and verify the signature by providing the appropriate secret or public key. All processing happens in your browser.