🔐 JWT Decoder & Verifier
Decode headers, payloads, inspect claims & expiry, verify HS256/RS256 signatures — 100% in-browser, zero data sent anywhere.
Understanding JWT Tokens: Decoding, Claims, and Signature Verification Explained
JSON Web Tokens have become the backbone of modern authentication. Whether you are building a REST API that issues access tokens, integrating with a third-party OAuth provider, or debugging why a user keeps getting logged out unexpectedly, the ability to decode and verify a JWT on-demand is something every developer needs in their toolkit. The problem is that most JWT tools either require you to paste tokens into a server-side form — which carries real privacy risks — or demand that you install a package just to read three base64url-encoded strings. Neither is acceptable when you are in the middle of a debug session.
This tool handles everything in the browser using the native Web Cryptography API. No data leaves your machine. The JavaScript executing the verification is the same JS engine your browser uses for everything else — it is auditable, it is fast, and it works offline once the page is loaded.
Anatomy of a JWT
A JWT is three base64url-encoded chunks joined by dots: header.payload.signature. The header tells you the signing algorithm and token type. The payload carries the claims — structured assertions about the subject, like their user ID, roles, token lifetime, and issuer. The signature is a cryptographic proof that the first two parts were not tampered with after the token was issued.
Base64url encoding is not encryption. Anyone who possesses a JWT can decode the header and payload in seconds without knowing the secret. This is by design: JWTs are meant to be read by resource servers that need to inspect claims before granting access. The secret only protects the integrity of the data, not its confidentiality. If you need to protect the payload contents, you need a JWE (JSON Web Encryption) token — a different standard entirely.
Standard Claims You Will Always Encounter
The JWT specification (RFC 7519) defines a set of registered claim names. Understanding what each one means is the foundation of debugging authentication problems:
sub (Subject) — the principal that is the subject of the JWT. In practice this is almost always the user ID from your database.
iss (Issuer) — identifies the entity that issued the token. Your backend will typically validate that this matches the expected issuer before trusting any other claims.
aud (Audience) — identifies the recipients for which the token is intended. A token issued for your mobile app should not be accepted by your admin dashboard, and validating the aud claim is how you enforce that boundary.
exp (Expiration Time) — a Unix timestamp after which the token must not be accepted. This is the single most common source of "why is my user getting logged out" bugs. The claim is in seconds since the Unix epoch, and off-by-one issues from confusing milliseconds with seconds are embarrassingly common.
iat (Issued At) — when the token was created. Useful for measuring token age and for implementing token revocation by rejecting tokens issued before a certain time (e.g., after a password reset).
nbf (Not Before) — the token must not be accepted before this time. Less commonly used than exp, but essential if you need to issue tokens in advance that only become valid at a scheduled future moment.
jti (JWT ID) — a unique identifier for the token. Required if you are maintaining a token blocklist for logout or revocation, since you need something to key the blocklist entries against.
HS256 vs RS256: Choosing the Right Algorithm
The algorithm declared in the header determines how the signature is computed and verified. The two algorithms you will encounter most often are HS256 and RS256, and they represent fundamentally different trust models.
HS256 (HMAC-SHA256) uses a shared secret. The same secret is used to sign the token and to verify it. This means every service that needs to verify JWTs must have access to the secret. It is simple, fast, and appropriate for single-service architectures or when all your services are under a single trust boundary. The critical security requirement: the secret must be a cryptographically random value of sufficient entropy. Using a human-readable password or a short string as an HS256 secret is a well-known vulnerability — attackers can brute-force short secrets offline using only the token, no network access required.
RS256 (RSA-SHA256) uses an asymmetric key pair. The private key signs the token and must be kept secret on the issuing server. The public key verifies the token and can be distributed freely to any service that needs to validate tokens. This is the right choice for distributed systems, microservices architectures, and public APIs. It allows you to publish your public keys at a JWKS (JSON Web Key Set) endpoint and let third-party services verify your tokens without ever exposing your private key.
The algorithm-confusion attack is worth knowing about: a malicious actor can modify the JWT header to change "alg":"RS256" to "alg":"HS256" and then sign the token using the public key as the HMAC secret. Vulnerable libraries that use the algorithm from the token header (rather than enforcing the expected algorithm server-side) will incorrectly validate the forged token. Always validate the algorithm on the server, never trust the alg claim blindly.
The "alg: none" Vulnerability
The JWT specification technically allows tokens with no signature by setting "alg":"none" and omitting the signature portion. Some early library implementations accepted these tokens without any verification whatsoever, allowing attackers to forge arbitrary claims. No production system should accept alg: none tokens. This tool correctly surfaces the algorithm in the header so you can immediately spot a token with this dangerous configuration.
Common JWT Debugging Scenarios
When an API returns a 401 Unauthorized response and you have the token in hand, the first three things to check are: Is the token expired (check exp against current time)? Is the token being sent to the correct audience (check aud)? And is the issuer what the server expects (check iss)? Expiry issues account for the majority of "my auth is broken" reports in production. Clock skew between servers — even a few seconds — can cause tokens to appear expired to one service but valid to another.
The second most common issue is testing with a token signed against a development secret while the production secret is different. Decoding the payload tells you nothing about whether the signature is valid — you have to actually run the HMAC or RSA verification with the correct key. That is exactly what this tool does for you locally, so you can rule out key mismatch without writing a test script or firing up Postman.
Security Note on In-Browser Verification
This tool is designed for development and debugging. It uses the Web Cryptography API (window.crypto.subtle), the same API that powers HTTPS in your browser and is available in every modern browser without polyfills. The cryptographic operations are implemented by the browser's native crypto engine — not JavaScript — so they are both correct and performant. The important thing: your JWT and your secret never touch any external server. This makes it safe to use with real tokens during a debug session, though you should still rotate any secret that was used in a shared environment immediately after debugging is complete.