Copied!
Back
Security Tool

JWT Decoder Online — Inspect JSON Web Tokens

Decode any JSON Web Token (JWT) instantly and inspect its header, payload, and claims. This free JWT decoder parses tokens per RFC 7519, displays standard claims (iss, sub, aud, exp, iat, nbf, jti) with human-readable timestamps, and highlights expiration in real time. Paste an OAuth 2.0 bearer token, an API access token, or any JWS to view its contents. All processing runs in your browser — your token never reaches a server, and nothing is logged.

Last updated: April 2026
jwt-decoder.tool

What is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe way to represent signed claims between two parties. Defined in RFC 7519, JWT became the dominant token format for stateless authentication after OAuth 2.0 (RFC 6749) adopted it as the default bearer token in OpenID Connect. Today, every major identity provider — Auth0, AWS Cognito, Firebase Auth, Okta, Keycloak, Azure AD — issues JWTs. Most modern APIs verify them on every request.

The killer feature is statelessness: instead of storing session data on the server (with the database lookup that implies), the server signs a token containing the user's identity, expiration, and permissions. Every subsequent request includes the token; the server verifies the signature with a single cryptographic check — no database hit. Trade-off: you can't revoke a token before it expires (without bringing back state).

A JWT is signed, not encrypted. Anyone holding the token can read its contents. The signature only proves the issuer hasn't been tampered with — it doesn't hide the data. This is why you should never put secrets in a JWT payload. If you need encryption, use JWE (JSON Web Encryption, RFC 7516) — the lesser-known sibling of JWT.

JWT structure — three parts separated by dots

Every JWT is exactly three Base64URL-encoded segments joined by periods:

jwt-format
<header>.<payload>.<signature>

// Real example (formatted for readability):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFuZWVzIiwiaWF0IjoxNzE5NTAwMDAwLCJleHAiOjE3MTk1MDM2MDB9
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header

A JSON object encoding the signing algorithm and token type. Decoded:

json
{ "alg": "HS256", "typ": "JWT" }

The alg field is critical — it tells the verifier which algorithm to use to validate the signature. Common values: HS256 (HMAC-SHA256, symmetric), RS256 (RSA + SHA-256, asymmetric), ES256 (ECDSA P-256). The optional kid (key ID) field tells the verifier which key to use when keys are rotated.

2. Payload

A JSON object containing the claims — statements about the user and the token itself. Mix of standard (registered) claims and custom (private) claims:

json
{
  "sub":   "1234",            // subject — the user
  "iss":   "https://auth.example.com",  // issuer
  "aud":   "api.example.com", // audience — who can use this
  "exp":   1719503600,        // expiration — Unix seconds
  "iat":   1719500000,        // issued at
  "nbf":   1719500000,        // not before
  "jti":   "a3f7c1...",       // unique token ID (for revocation lists)
  "role":  "admin",           // custom claim
  "scope": "read write"       // custom claim
}

3. Signature

The signature is computed over the encoded header and payload using the algorithm specified in the header. For HS256:

signature-formula
signature = HMAC-SHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

The verifier recomputes this over the received header and payload using its copy of the secret (or public key for RS256/ES256). If the signatures match, the token is authentic. If not, reject it.

JWT signing algorithms — HS256 vs RS256 vs ES256

Choosing the algorithm depends on whether you control both ends of the trust relationship.

AlgorithmTypeKeyWhen to useTrade-offs
HS256 (HMAC-SHA256) Symmetric Shared secret (same key signs and verifies) Single-service apps where the signer and verifier are the same backend Anyone who can verify can also forge tokens. Don't use across multiple services.
RS256 (RSA-PKCS1-v1.5 + SHA-256) Asymmetric RSA private key signs, public key verifies (2048+ bit) Multi-service architectures, third-party integrations, OpenID Connect Larger signatures (256 bytes), slower than HS256, but verifiers don't need the secret.
ES256 (ECDSA P-256 + SHA-256) Asymmetric Elliptic curve private key signs, public key verifies Same as RS256 but smaller and faster — modern preferred choice Slightly newer; less universal library support than RS256.
EdDSA (Ed25519) Asymmetric Ed25519 keys — fast, modern New systems where library support exists Best modern choice, but check verifier support before adopting.
none (no signature) Never in production. Source of multiple historical vulnerabilities. Servers must explicitly reject alg: none.

Rule of thumb: use RS256 or ES256 across services (so verifiers don't need your secret), HS256 only inside a single service, and never none.

Standard JWT claims explained

RFC 7519 §4.1 defines seven registered claims. Every JWT library understands these, so use them rather than custom equivalents.

ClaimFull nameTypePurpose
issIssuerString / URLWho issued this token. Verifiers should check this matches a trusted issuer.
subSubjectStringWhom the token is about. Usually a user ID.
audAudienceString or arrayWho is allowed to use this token. Verifiers must check this matches their service.
expExpirationNumericDate (Unix seconds)After this time, the token is invalid. Mandatory in practice.
nbfNot BeforeNumericDateToken is not valid before this time. Useful for delayed activation.
iatIssued AtNumericDateWhen the token was issued. Useful for revocation by issue time.
jtiJWT IDStringUnique identifier for the token. Enables revocation lists.

Custom claims should be namespaced when used across organizations — RFC 7519 §4.3 recommends using a URI-prefixed name like "https://example.com/role": "admin" to avoid collisions with future registered claims.

JWT verification flow — the complete checklist

Decoding a JWT is trivial. Verifying it correctly is what matters. A complete server-side check covers eight steps:

  • 1. Parse the three parts. Split on dots; reject if not exactly three segments.
  • 2. Decode header. Base64URL-decode and parse JSON. Read alg and kid.
  • 3. Validate the algorithm. Whitelist allowed algorithms in your library. Never trust alg from the token alone — that's the alg-confusion attack. Configure your library to accept only RS256 (or whichever you use).
  • 4. Look up the key. For HS256, your shared secret. For RS256/ES256, fetch the public key by kid from your JWKS endpoint (/.well-known/jwks.json).
  • 5. Verify the signature. Recompute it over header.payload and compare with constant-time equality.
  • 6. Check expiration. Reject if exp is missing OR now() > exp (with a small leeway, e.g. ±60 seconds for clock skew).
  • 7. Check audience and issuer. Verify iss matches your trusted issuer. Verify aud contains your service's identifier.
  • 8. Check revocation if you maintain a list. Look up jti in your revocation cache; reject if present.
This decoder does steps 1–2 only. It exists to let you inspect a token. Always verify on the server with a battle-tested library: jose (JS/Node), PyJWT (Python), jjwt (Java), golang-jwt/jwt (Go), or your auth provider's SDK.

Verifying JWTs in 8 programming languages

Node.js (jose)

node.js
import { jwtVerify, createRemoteJWKSet } from 'jose';

// Auto-fetches keys from the issuer's JWKS endpoint
const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));

const { payload } = await jwtVerify(token, JWKS, {
  issuer:   'https://auth.example.com',
  audience: 'api.example.com',
  algorithms: ['RS256'],   // whitelist!
});
console.log(payload.sub);

Python (PyJWT)

python
import jwt

# Symmetric (HS256)
decoded = jwt.decode(token, secret, algorithms=["HS256"],
                     audience="api.example.com",
                     issuer="https://auth.example.com")

# Asymmetric (RS256) with PyJWT >= 2.0
decoded = jwt.decode(
    token, public_key, algorithms=["RS256"],
    audience="api.example.com",
)
print(decoded["sub"])

PHP (firebase/php-jwt)

php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// HS256
$decoded = JWT::decode($token, new Key($secret, 'HS256'));

// RS256
$decoded = JWT::decode($token, new Key($publicKey, 'RS256'));
echo $decoded->sub;

Java (JJWT)

java
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

Jws<Claims> jws = Jwts.parser()
    .verifyWith(publicKey)            // RSA public key
    .requireIssuer("https://auth.example.com")
    .requireAudience("api.example.com")
    .build()
    .parseSignedClaims(token);

String userId = jws.getPayload().getSubject();

Go (golang-jwt/jwt)

go
import "github.com/golang-jwt/jwt/v5"

token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
    if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
        return nil, fmt.Errorf("unexpected algorithm: %v", t.Header["alg"])
    }
    return publicKey, nil
}, jwt.WithAudience("api.example.com"),
   jwt.WithIssuer("https://auth.example.com"))

Rust (jsonwebtoken)

rust
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};

let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&["api.example.com"]);
validation.set_issuer(&["https://auth.example.com"]);

let key = DecodingKey::from_rsa_pem(public_key_pem)?;
let token = decode::<Claims>(token_str, &key, &validation)?;

Ruby (jwt gem)

ruby
require 'jwt'

decoded = JWT.decode(token, public_key, true, {
  algorithm: 'RS256',
  iss: 'https://auth.example.com',
  verify_iss: true,
  aud: 'api.example.com',
  verify_aud: true,
})
payload = decoded[0]

C# / .NET

csharp
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

var handler = new JwtSecurityTokenHandler();
var validation = new TokenValidationParameters {
    ValidIssuer = "https://auth.example.com",
    ValidAudience = "api.example.com",
    IssuerSigningKey = new RsaSecurityKey(publicKey),
    ValidAlgorithms = new[] { "RS256" },
};
handler.ValidateToken(token, validation, out var validatedToken);

Common JWT vulnerabilities — and how to avoid them

1. Algorithm confusion (alg: none)

Some libraries accept "alg": "none" in the header, which means "no signature." An attacker forges a token with alg: none and an empty signature; if your library trusts the header, the token is "valid." Fix: always whitelist allowed algorithms server-side. Never accept none.

2. RS256 → HS256 substitution

If your verifier accepts both HS256 and RS256, an attacker can take your public RSA key (which is not a secret), use it as the HMAC secret, and forge a token signed as HS256. Your library validates with the public key as if it were an HMAC secret. Fix: hard-code a single algorithm per endpoint, or only allow algorithms that match the key type.

3. Unverified kid path traversal

The kid header is attacker-controlled. If your code does readFile("/keys/" + kid + ".pem"), an attacker sets kid to ../../etc/passwd and reads arbitrary files. Fix: look up keys in a whitelist or hashmap, never construct file paths from kid.

4. Missing or wrong audience check

Token issued for service A gets replayed against service B. If service B doesn't check aud, it accepts the token. Fix: always validate aud matches your service identifier.

5. Long expiration windows

Tokens with exp 30 days out are catastrophic if leaked. Fix: short access tokens (5–60 minutes) + refresh tokens stored server-side. Rotate refresh tokens on use.

6. Storing secrets in payload

Payloads are Base64URL, not encrypted. Anyone with the token reads them. Fix: only put non-sensitive identifiers in JWTs. Use JWE if you need encryption.

7. Client-side signature verification

"Verifying" a JWT in the browser is theater — the user controls the browser. Fix: always verify on the server. Browser-side, you can read the payload to display the user's name, but never base authorization decisions on it.

JWT vs sessions vs API keys — when to use what

MechanismStateful?Best forTrade-offs
JWT (Bearer) No (stateless) Microservices, mobile/SPA → API, OpenID Connect, federated identity Hard to revoke before expiration. Larger than session cookies.
Session cookie + server store Yes Traditional web apps, immediate revocation, server-rendered pages DB lookup per request. Tightly tied to one origin.
API key Yes (DB lookup) Server-to-server APIs, long-lived integrations, public-facing APIs Long-lived. Compromise = rotate key. No claims.
OAuth 2.0 access token (JWT or opaque) Depends on issuer Third-party integrations, "Login with X" Spec compliance overhead. Requires identity provider.

The wider industry has moved toward short-lived JWTs + refresh tokens for client-server auth, but plain old session cookies are still the simpler choice for monolithic apps where you don't need cross-service authentication.

Decode JWT, parse JWT, decode bearer token — what people search for

"Decode JWT", "jwt decode", "jwt token decode", "parse jwt", "parse jwt token", "json web token decode", "json web signature decode", "bearer token decode", "decode oauth token" — Google clusters these together because they're all the same intent: paste an encoded token, see the decoded header and payload, check expiry. The decoder above handles every form. Each section below answers one of the recurring sub-questions.

Bearer token decode — what an Authorization header actually contains

The HTTP header Authorization: Bearer eyJhbGciOiJIUzI1NiIs… carries a JWT in the value after Bearer . Strip the Bearer prefix and paste the rest into the decoder above; the three dot-separated segments expand into header (algorithm + key id), payload (claims like sub, exp, scope), and signature (verifiable only with the issuer's secret or public key). For inspecting the raw HTTP exchange that produced the token — refresh-token flow, error responses, token-rotation logic — pair the decoder with the HTTP request builder.

Decode OAuth token vs JWT — when they're the same and when they're not

An OAuth 2.0 access token can be a JWT, but doesn't have to be. The OAuth spec is silent on token format — issuers choose whether to use opaque random strings (Auth0 by default in some flows, Stripe API keys, GitHub PATs) or self-contained JWTs (Auth0 with an audience claim, Okta, Cognito). If your token starts with eyJ, it's a Base64-encoded JWT — paste it above. If it's an opaque random string (at_2NhwQDdHO… style), it's not a JWT and decoding it client-side returns nothing useful — you must call the issuer's introspection endpoint. The decoder above shows clear "not a JWT" output for opaque tokens so you don't waste time searching for a hidden payload.

JWT token validator — verifying signatures, audience, expiry

Decoding shows you the contents; validation proves the token wasn't tampered with. The five checks every server should run on every request:

  1. Signature. Verify with the issuer's public key (RS256, ES256) or shared secret (HS256). Never trust a token you couldn't verify.
  2. exp (expiry). Reject tokens past their expiration. Allow ~30 seconds clock skew.
  3. iat (issued at) and nbf (not before). Reject tokens issued in the future or not yet valid.
  4. aud (audience). Reject tokens minted for a different service.
  5. iss (issuer). Reject tokens from an issuer you don't trust.

The decoder above runs all five client-side when you provide the public key or secret — useful for debugging "this token works locally but not in prod" issues. To generate test tokens with controlled exp/aud/iss claims, use the JWT generator.

JWT.io decode alternative — the same workflow without sending tokens off-device

jwt.io's decoder runs in-browser too, but the URL pattern jwt.io/#token=… means your token sits in browser history (and any tracking pixels that read history). The decoder above never builds a URL with the token in it — paste in the textarea and decoding happens on the keystroke event, no navigation, no URL bar update. For quick spot-checks of a production token, this difference matters; for development tokens, either tool works. The deeper feature parity comparison is in the section below.

JSON Web Signature (JWS) decode vs JSON Web Encryption (JWE)

"JSON web signature decode" is a more precise way to say "JWT decode" for tokens that are signed but not encrypted (the typical case). A JWS has three Base64URL parts joined by dots; the payload is plaintext JSON anyone can read after Base64 decoding. A JWE has five parts and the payload is encrypted — you can see the header but the payload requires the recipient's private key to read. The decoder above handles JWS; for JWE tokens it surfaces the protected header and warns that payload decryption requires the receiver's key (which never belongs in a browser-side tool).

JWT best practices

  • Set short exp. 5–15 minutes for access tokens. Use refresh tokens for longer sessions.
  • Whitelist algorithms. Configure your library to accept only one algorithm per verification path. Never trust the alg in the header.
  • Verify iss and aud always. These are not optional.
  • Use HTTPS-only cookies (HttpOnly; Secure; SameSite=Strict) to store JWTs in browsers if possible — far safer than localStorage, which is vulnerable to XSS.
  • Rotate signing keys. Use kid to identify keys. Run a JWKS endpoint that publishes current public keys.
  • Don't roll your own JWT library. Cryptography is full of subtle pitfalls. Use jose, PyJWT, jjwt, etc.
  • Use this decoder for inspection only. Real verification belongs on a server with a real library and the proper key material.
  • Don't store JWTs in URLs or query parameters. They get logged in server access logs, browser history, and analytics. Use Authorization: Bearer headers.

How this JWT decoder compares to popular alternatives

"JWT decoder" is one of the most-searched developer-tool queries. The space is dominated by jwt.io (Auth0's reference tool, around since 2015), with several modern alternatives competing on different angles. Here's how this tool stacks up so you can pick the right fit for your workflow.

CapabilityFreeDevTool /jwt-decoderjwt.iotoken.devjsonwebtoken.io
Decode header + payload
Auto-flag expired tokens (exp in past)⚠️ Partial
Verify HMAC signatures (HS256/384/512)✅ Web Crypto API
Verify RSA signatures (RS256/384/512)Roadmap
Verify ECDSA signatures (ES256/384/512)Roadmap⚠️ Partial⚠️ Partial
Generate / sign new tokens (paired tool)/jwt-generator⚠️ Limited
Browser-only — token never leaves your tab⚠️ Unclear in TOS
Open-source / inspectable code✅ GitHub⚠️ Closed
Display ads on the tool page❌ Ad-free✅ Ads present
Paired with long-form RFC 7519 / 9562 implementation guide26-min read⚠️ Quick reference
Part of a multi-tool dev catalog (Base64, hash, regex, JSON, …)✅ 50 tools, 4 in-depth guides❌ JWT only❌ JWT only❌ JWT only
Sign-up required to save / share❌ No accounts

When to pick which tool

  • Pick FreeDevTool /jwt-decoder when: you want a single browser-based JWT decoder paired with a generator (test signing) and an in-depth implementation guide, and you're already using other tools in the FreeDevTool catalog (Base64, hash, regex, JSON formatter) and want a consistent zero-signup workflow.
  • Pick jwt.io when: you specifically need RSA / ECDSA signature verification right now, or you want the brand-trust signal of using Auth0's reference tool when sharing debugging output with a teammate or vendor.
  • Pick token.dev when: you want a modern, single-purpose JWT debugger with a clean UI focused only on the JWT use case.

This is an honest comparison rather than a marketing claim. RSA / ECDSA verification is on the roadmap for FreeDevTool but isn't shipped today; for those algorithms, jwt.io remains the strongest choice. Where FreeDevTool genuinely differentiates is the tool + paired generator + 26-minute guide + 50-tool ecosystem combination — useful when JWT debugging is one task in a longer dev workflow, not a one-off lookup.

Frequently linked competitor pages

If you're researching JWT decoder alternatives, you're probably also looking at: jwt.io, token.dev, JsToolSet JWT decoder. Each has trade-offs documented above.

How to use the JWT decoder

JWTs are everywhere — OAuth bearer tokens, Auth0/Cognito sessions, signed API requests. The decoder splits a token into its three Base64-URL parts (header, payload, signature) and shows the claims human-readably. Decoding happens entirely in your browser; tokens never leave the page.

Common JWT mistakes to avoid

Frequently Asked Questions

What is the difference between decoding and verifying a JWT?
Decoding a JWT extracts the header and payload by Base64URL-decoding them — no secret key is needed. Verifying checks the cryptographic signature against a secret (HMAC) or public key (RSA/ECDSA) to confirm the token hasn't been tampered with. This tool decodes JWTs for inspection purposes. You should always verify signatures on your server before trusting any claims in production, as defined in RFC 7519.
Is it safe to decode a JWT token online?
It depends on the tool. This JWT decoder runs 100% in your browser using JavaScript — no data is ever sent to any server. Your token stays on your device. Avoid online decoders that submit tokens to backend APIs, because JWTs often contain user IDs, email addresses, roles, and session data that could be logged or intercepted.
Can you decode a JWT without the secret key?
Yes. A JWT consists of three Base64URL-encoded parts: header, payload, and signature. The header and payload are not encrypted — they are simply encoded. Anyone can decode them without any key. The secret key is only needed to verify the signature. This is why you should never store sensitive data like passwords or credit card numbers directly in a JWT payload.
What does the exp claim mean in a JWT?
The exp (expiration time) claim is a registered JWT claim defined in RFC 7519. It specifies a UNIX timestamp (seconds since January 1, 1970 UTC) after which the token must not be accepted. If the current time exceeds the exp value, the token is expired. Servers should always check this claim. Common expiration times range from 15 minutes (access tokens) to 7 days (refresh tokens).
Should I store sensitive data in a JWT payload?
No. JWT payloads are Base64URL-encoded, not encrypted. Anyone who intercepts a JWT can read its full payload. Store only non-sensitive identifiers (user ID, role, permissions) in the payload. For truly confidential data, use JWE (JSON Web Encryption) defined in RFC 7516, or keep the data server-side and reference it via a claim ID.
What are the registered claims in a JWT?
RFC 7519 defines seven registered claims: iss (issuer), sub (subject), aud (audience), exp (expiration time), nbf (not before), iat (issued at), and jti (JWT ID). These are optional but recommended for interoperability. Most OAuth 2.0 and OpenID Connect implementations use these claims.

Browse all 50 free developer tools

All tools run in your browser, no signup required, nothing sent to a server.