Decoding Is Not Verifying
A JWT is three Base64url parts joined by dots, header.payload.signature, and anyone can read the first two without a key. Paste a token above to see its header and claims as JSON, with exp, iat and nbf turned into dates. This tool decodes only: it does not verify the signature, so never trust a token just because it decodes.
The header and payload of a signed JWT are only encoded, not encrypted, so they can be read without any key. Whether the token is genuine depends on the signature, and checking it needs the issuer's secret or public key. This page does the first job only. Decoding runs in your browser; the token is not sent anywhere.
A Token Taken Apart
This is the well-known example token (line breaks added):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Header (36 characters) decodes to
{"alg":"HS256","typ":"JWT"}. - Payload (74 characters) decodes to
{"sub":"1234567890","name":"John Doe","iat":1516239022}. 74 is not a multiple of 4, so a decoder adds==before decoding; JWTs always drop the padding. - iat 1516239022 is 18 January 2018, 01:30:22 UTC.
- Signature (43 characters) is an HMAC-SHA256 of the first two parts joined by a dot. It checks out only with the secret it was made with, in this case
your-256-bit-secret.
Because the payload is only Base64url, a JWT is no place for passwords or personal data you would not show the user.
Registered Claims Quick Reference
| Claim | Meaning | Example | What the server should check |
|---|---|---|---|
| iss | Issuer | https://auth.example.com | Exactly matches the issuer you trust |
| sub | Subject | user_123456 | Identifies the user or client |
| aud | Audience | https://api.example.com | Contains your own API's identifier |
| exp | Expiration time | 1747622400 | Current time is before it |
| nbf | Not before | 1716000000 | Current time is at or after it |
| iat | Issued at | 1716000000 | Not in the future; optional maximum age |
| jti | JWT ID | 8f14eb8d-97f9-... | Not seen before, if you block replays |
| alg (header) | Signing algorithm | HS256, RS256, ES256 | Is on your allow list; never accept none |
| kid (header) | Key ID | key-123 | Picks the key from your own key set |
The examples come from the tool's sample token, whose exp of 1747622400 is 19 May 2025, 02:40 UTC, which is why the sample shows as expired.
Timestamps Are Seconds, Not Milliseconds
exp, nbf and iat are NumericDate values: whole seconds since 1 January 1970 UTC. JavaScript's Date.now() returns milliseconds, so a common bug is writing exp: Date.now() + 3600000. The result has 13 digits, and read as seconds, 1747622400000 lands in the year 57349; the token effectively never expires. Use Math.floor(Date.now() / 1000) + 3600 for one hour. As a quick check, a current timestamp in seconds has 10 digits.
Decode Errors and Their Causes
- "Must have at least two parts": only part of the token was copied. A signed JWT has exactly two dots.
- Five parts: this is an encrypted JWE, not a signed JWT. The payload cannot be read without the decryption key.
- Invalid header or payload: a character was lost or changed when copying, or the string is an opaque access token that only looks like a JWT. Not every OAuth access token is a JWT.
- "Bearer" or quotes around it: the tool now strips a leading
Bearerand surrounding quotes for you.
JWT parts use the URL-safe Base64 alphabet; you can decode a single part in the Base64 decoder too, and turn exp values into dates with the Unix timestamp converter.
JWT Guide
header.payload.signature. The header describes the token type and signing algorithm. The payload contains claims (statements about the user and metadata). The signature verifies the token has not been tampered with. JWTs are used for authentication (stateless sessions: the server doesn't need to store session state), API authorization (OAuth 2.0 Bearer tokens), and information exchange between microservices.iss (Issuer): who issued the token. sub (Subject): who the token is about, usually a user ID. aud (Audience): who the token is intended for. exp (Expiration Time): Unix timestamp after which the token is invalid. nbf (Not Before): Unix timestamp before which the token must not be accepted. iat (Issued At): when the token was issued. jti (JWT ID): unique identifier for the token, useful for preventing replay attacks. Public claims are registered in the IANA JWT Claims Registry. Private claims are custom, application-specific.jsonwebtoken (Node.js), PyJWT (Python), or java-jwt (Java)./.well-known/jwks.json) that exposes the public keys used to verify JWTs. OAuth 2.0 providers (Auth0, Okta, Google, AWS Cognito) all publish a JWKS endpoint. Verification flow: (1) get the kid (Key ID) from the JWT header, (2) fetch the matching public key from the JWKS endpoint, (3) verify the JWT signature using that key. This allows key rotation without redeploying services. Libraries like jwks-rsa (Node.js) cache keys and handle rotation automatically. The alg in the JWKS must match the alg in the JWT header; always validate this to prevent algorithm confusion attacks.alg header from RS256 to HS256 and signs with the public key as the HMAC secret. Fix: always explicitly specify the expected algorithm. None algorithm: setting alg: none to bypass signature verification. Fix: reject tokens with alg: none. Weak secrets: HS256 with short secrets can be brute-forced. Fix: use secrets of at least 256 bits. Missing validation: not checking exp, iss, or aud. Fix: always validate all relevant claims. Sensitive data in payload: the payload is encoded, not encrypted. Fix: never store passwords or private data in JWT payload; use JWE (JSON Web Encryption) if encryption is needed.JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))). Node.js: const [h,p] = token.split('.').slice(0,2).map(p => JSON.parse(Buffer.from(p,'base64url'))). Python: import base64, json; json.loads(base64.urlsafe_b64decode(token.split('.')[1]+'==')). For production, always use a library that also verifies the signature: Node.js jsonwebtoken, Python PyJWT, Go golang-jwt/jwt, Java jjwt, .NET System.IdentityModel.Tokens.Jwt. Decoding without verification is only appropriate for debugging and display purposes, never for authorization.jwt.verify(token, secret, { algorithms: ['HS256'] }) in the Node.js jsonwebtoken package, and check exp, iss and aud at the same time.