StringToolsStringTools

JWT Decoder & Encoder: Decode, Verify and Sign

Paste a JSON Web Token to read its header, payload and claims with the expiry worked out in plain English — then verify the signature against the real key, or sign a token of your own. All 13 JWA algorithms, a built-in key-pair generator, and every byte of it running in this page. Nothing is uploaded, not your tokens and not your keys.

Read this before you trust anything on this page

  • Decoding is not verification. Anyone can craft a token containing any claims they like, so a decoded payload proves nothing on its own. It becomes evidence only once the signature checks out against the right key — paste that key into Verify the signature below and the check runs for real.
  • A JWT payload is encoded, not encrypted. Base64URL is reversible by anyone — exactly what is happening on this page. Never put a password, a card number, or anything else secret in a JWT payload.
  • Nothing you paste leaves your browser. Decoding, signing and verification all run in this page’s JavaScript via the Web Crypto API. Your tokens, secrets and private keys are never uploaded, logged, or stored. A token is still a live credential until it expires — revoke it if you have shared it anywhere else.

A Bearer prefix, surrounding quotes, and line breaks are ignored, so you can paste straight from an Authorization header or a log line.

Checking…alg: HS256typ: JWTkid: 2026-09-key-01signature not verified

HS256HMAC with SHA-256 — symmetric. The same shared secret both signs and verifies.

Segment sizes

PartEncodedDecoded
Header67 ch50 B
Payload354 ch265 B
Signature43 ch
Whole token466 ch

Base64URL characters are one byte each, so the encoded count is also the byte count on the wire. Big tokens can overflow a server’s header limit (often 8 KB).

Header

How the token is signed — the algorithm and key hint.

{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "2026-09-key-01"
}

Payload

The claims. Readable by anyone holding the token.

{
  "sub": "user_8f21c6b4",
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "roles": [
    "admin",
    "billing:read"
  ],
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "jti": "f3a9c07e-4b21-4d6e-9f8a-1c2b3d4e5f60",
  "iat": 1788255000,
  "nbf": 1788255000,
  "exp": 1989403200
}
Registered claims, in plain English
ClaimRaw valueMeans
iat

Issued at — when the token was created

17882550002026-09-01 09:30:00 UTC
nbf

Not before — not valid before this moment

17882550002026-09-01 09:30:00 UTC
exp

Expiration time — not valid at or after this moment

19894032002033-01-15 12:00:00 UTC
iss

Issuer — who created and signed the token

https://auth.example.comThe server should check this matches the issuer it trusts.
sub

Subject — who or what the token is about

user_8f21c6b4The identifier the issuer uses for this user or client.
aud

Audience — who the token is intended for

https://api.example.comA service should reject the token if its own identifier is not in here.
jti

JWT ID — a unique id for this token

f3a9c07e-4b21-4d6e-9f8a-1c2b3d4e5f60Used to spot replays or to revoke a single token.
Times are read from the claims exactly as written and shown in UTC. These dates describe what the token says about itself — a real verifier still has to check the signature, the issuer, and the audience before trusting any of it. This token also carries 3 non-registered claims (name email roles ) — see the full payload above.

Signature

Shown as it appears in the token. It is raw bytes, not text, so there is nothing to decode — but you can check it below.

2L316-Y9_CNrh1DAHK4mKVBHu2RCi4bBw3J4-kNTPSQ

Verify the signature

Supply the key this token was signed with and the check runs here, in your browser, using the Web Crypto API. The key is never uploaded. The sample token is really signed with stringtools-demo-secret-256-bits-long-enough — to watch a real verification pass, then change one character to watch it fail.

Follows RFC 7519 (JWT), RFC 7515 (JWS), RFC 7518 (algorithms) and RFC 8037 (EdDSA). Segments are decoded with the Base64URL alphabet (- and _ in place of + and /) and the stripped = padding is restored before decoding, so tokens that break naive decoders still read correctly here. Signing and verification use the browser’s native Web Crypto API — HMAC, RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA and Ed25519 — so the result is a real cryptographic check, not a simulation. Everything runs on your machine; nothing is uploaded, logged, or stored.

TL;DR

A JWT is three Base64URL segments joined by dots. The first two are encoded, not encrypted— anyone holding the token can read them, so a JWT payload is never a place for secrets. Decoding needs no key at all; verifying needs the signing key. Decoded on its own, a token that says "role": "admin" is not proof of anything — give this page the signing key and it will tell you whether that claim is genuine. On the server, always pin the algorithm you expect instead of reading alg out of the attacker-supplied header. And treat any token you paste anywhere as burned: it is a live credential until it expires.

Decoding is not verification, and the gap is where the bugs live

Almost every “JWT debugger” blurs this line, and the blur causes real outages and real breaches. Decoding a JWT is a pure text transformation: swap the Base64URL alphabet back, restore the stripped padding, run it through a Base64 decoder, parse the JSON. No key is involved, no secret is needed, and it works on a token you invented thirty seconds ago in a text editor. Verification is a different operation entirely — it recomputes the signature over the exact bytes base64url(header) + "." + base64url(payload) using the signing key, compares it to the third segment, and only then does the payload mean anything.

QuestionDecoding answers it?Needs verification?
What claims does this token carry?YesNo
When does the exp claim say it expires?YesNo
Which key was it signed with (the kid hint)?YesNo
Did the issuer actually mint this token?NoYes — needs the key
Has anyone edited the payload since it was issued?NoYes — needs the key
Is this user really an admin?NoYes — needs the key

Everything in the right-hand column is unanswerable on this page, by design. The expiry badge above is a statement about what the exp number literally says, not a verdict on whether the token is genuine — a forged token can carry any expiry an attacker likes. Use a decoder to understand a token; use a maintained library with the real key, server-side, to trust one.

Three segments, two of them readable by anyone

The structure is header.payload.signature. The first two are JSON objects run through Base64URL — the URL-safe variant of Base64 that uses - and _ where standard Base64 uses + and /, with the trailing = padding stripped. That padding removal is exactly why pasting a JWT segment into a generic Base64 decoder often fails; the decoder above adds the padding back before decoding.

  • Header— metadata about the signature. alg names the algorithm, typ is usually JWT, and kid is a hint telling the verifier which key from the issuer’s key set to use. This segment is supplied by whoever produced the token, which makes it untrusted input, not instructions.
  • Payload— the claims. A JSON object of registered claims (iss, sub, exp…) plus whatever custom fields the issuer adds, such as roles or a tenant id.
  • Signature— raw bytes, not text. Decoding it gives you binary noise, which is why the tool shows it as-is rather than pretending to translate it.

Encoded is not encrypted — the single most costly JWT misunderstanding

Base64URL is a transport encoding, reversible by anyone, with no key and no secret. What you are watching this page do to your token, any attacker who intercepts it can do just as easily — and so can the user it was issued to, in their own browser console. A signature makes a payload tamper-evident; it does not make it private. So never put a password, an API key, a card number, a national ID, a full date of birth or an internal database secret in a JWT payload. If the contents genuinely must be hidden, you need a JWE (an encrypted token, five segments instead of three) — and the tool above will tell you when you have pasted one, because a JWE cannot be read without its decryption key.

Trivia that is genuinely useful when grepping logs: nearly every JWT starts with eyJ, because that is what {"— the opening of any JSON object with a quoted key — looks like once Base64-encoded. If a string in a log starts with eyJ and contains two dots, you are almost certainly looking at a leaked token.

The seven registered claims, and what a verifier should do with each

RFC 7519 registers seven claim names. All of them are optional — a JWT with an empty payload is still structurally valid — but the three time claims use a specific format called NumericDate: seconds since 1970-01-01 UTC, not milliseconds. Passing JavaScript’s Date.now() straight into exp is one of the most common home-rolled-issuer bugs there is, and it produces tokens that expire tens of thousands of years from now. The decoder flags that case explicitly.

ClaimNameWhat it holdsTypical use on the server
issIssuerString or URI naming who minted the tokenReject unless it exactly matches an issuer you trust. Also decides which key set to fetch.
subSubjectWho or what the token is about, usually an opaque user idThe identity you act on — after verification. Unique only within one issuer, so key your records on iss + sub.
audAudienceOne string, or an array, naming the intended recipientsReject if your own service identifier is not in it. This is what stops a token meant for service A being replayed against service B.
expExpiration TimeNumericDate — seconds since the Unix epochReject at or after this instant, allowing a small clock-skew grace (seconds, not hours). A token with no exp never expires by itself.
nbfNot BeforeNumericDateReject before this instant. Used for tokens issued ahead of the moment they become usable; often just equal to iat.
iatIssued AtNumericDateMostly informational. Useful for “force re-auth if older than N minutes” policies and for spotting clock drift between services.
jtiJWT IDA unique identifier for this one tokenReplay detection and single-token revocation — but only if you keep a server-side list of used or revoked ids, which reintroduces state.

Everything else in a payload is a private or public claim invented by the issuer: email, roles, scope, tenant_id and so on. Those carry no standard meaning, so two systems can use the same name for different things — another reason to check iss before you read a custom claim.

Which algorithm signed it, and what key you need to check it

The practical divide is symmetric versus asymmetric. With HS*, one shared secret both signs and verifies — so every service that can check a token can also forge one, which is fine inside a single application and a liability across an organisation. With RS*, PS*, ES* and EdDSA, only the issuer holds the private key and everyone else verifies with a public key, usually published at a JWKS endpoint. A useful side effect: the signature length is fixed per algorithm, so the segment-size table in the tool above often tells you whether a token’s header is being honest about its own alg.

algFull nameTypeKey needed to verifySignature size
HS256HMAC with SHA-256SymmetricThe same shared secret that signed it32 B → 43 chars
HS384HMAC with SHA-384SymmetricThe same shared secret48 B → 64 chars
HS512HMAC with SHA-512SymmetricThe same shared secret64 B → 86 chars
RS256RSASSA-PKCS1-v1_5 with SHA-256AsymmetricThe issuer’s RSA public key, normally from JWKSKey-sized — 256 B → 342 chars at 2048-bit
PS256RSASSA-PSS with SHA-256AsymmetricThe same RSA public key; PSS is the modern paddingKey-sized, as RS256
ES256ECDSA on curve P-256 with SHA-256AsymmetricThe issuer’s EC public key64 B → 86 chars
EdDSAEdwards-curve DSA, usually Ed25519AsymmetricThe issuer’s Ed25519 public key64 B → 86 chars
noneUnsecured — no signature at allNone. There is nothing to verify.0 — empty, trailing dot

HS*, RS*, PS* and ES* are registered in RFC 7518; EdDSA was added later by RFC 8037. The ES* and EdDSA signatures are two fixed-width halves concatenated, which is why their length never varies — unlike RSA, where the signature is exactly as long as the modulus. This table is a reference, not something the decoder acts on: it reads the alg value and describes it, but it holds no keys and runs no signature check.

The alg: none trap, and why the header must never pick the algorithm

RFC 7519 allows an “Unsecured JWT”: alg set to none, an empty signature segment, and a trailing dot where the signature would be. It exists for cases where integrity is already guaranteed by some other layer. It became notorious because of how libraries used it. A verify function whose signature looked like verify(token, key) would read alg out of the header to decide which check to run — and if the header said none, the honest implementation of “check a none signature” is to do nothing and return success. An attacker could take a real token, rewrite the payload to "role": "admin", set alg to none, drop the signature, and be let straight in. A cluster of popular JWT libraries shipped this behaviour until a 2015 disclosure forced the fix; the Node ecosystem’s instance is catalogued as CVE-2015-9235.

The subtler cousin is algorithm confusion. Suppose a service issues RS256 tokens and verifies them with the issuer’s public key — which is, by definition, public. An attacker rewrites the header to HS256 and signs their forged token using those public key bytes as the HMAC secret. A library that dispatches on the header will now compute an HMAC with the very same public key, get a match, and accept the forgery. The root cause in both attacks is identical: the token got to choose how it would be checked.

What a correct verifier does

  • Pins an explicit allowlist of accepted algorithms at the call site — typically exactly one — and rejects anything else before touching the payload.
  • Refuses none unconditionally in any context where the token is used as a credential.
  • Uses the header’s kid only to look up a key in a set it already trusts, never as a file path, a URL, or a value interpolated into a query.
  • Ignores jku and x5u headers that point at a key set, unless the URL is on a strict allowlist. Otherwise an attacker just hosts their own keys.
  • Checks exp, nbf, iss and aud after the signature passes — a valid-looking date on an unverified token means nothing.

The decoder above flags alg: none in red and warns when a signature segment is empty while the header claims a real algorithm — but that is a readability aid for you, not a security control. It cannot tell a forged token from a genuine one, and nothing on this page should be treated as a pass or a fail.

Signing your own tokens, and the three tests worth running

The Encode & sign tab turns the tool around: edit the header and payload as JSON, pick an algorithm, supply a key, and it produces a real signed token. The JSON is compacted before encoding, exactly as a library would, so the token you get here is byte-for-byte what your own code should produce from the same input — which is what makes it useful for comparing against a token your service emitted. For RS, PS, ES and EdDSA you do not need to bring a key at all: Generate a key pair creates one with crypto.subtle.generateKey, hands you both halves in PEM form, and keeps neither.

The reason to sign a token by hand is almost always to attack your own verifier before somebody else does. Three tests are worth running against any service that accepts JWTs:

  1. The unsigned token. Set alg to none, sign nothing, and send the result. A correct verifier rejects it outright. If yours returns 200, an attacker can mint any identity they like, and no key is involved.
  2. The swapped algorithm. Take a token your RS256 service issued, re-sign it here as HS256 using the service’s own public key as the HMAC secret, and send that. A verifier that reads alg out of the header instead of pinning it will treat the public key — which is not secret — as a shared secret and accept the forgery.
  3. The expired token. Set exp to a time in the past and confirm you get a 401. It is a two-second check, and a surprising number of services skip the claim entirely because their library only validates exp when you ask it to.

Run these against systems you are responsible for. A token you sign here is only as meaningful as the key behind it: with a key nobody trusts it is a harmless test fixture, and with a production signing key it is a live credential that should never have been pasted into a web page in the first place — including this one.

A JWT is a live credential, so where you paste it matters

An access token is a bearer credential: whoever holds it is treated as you, no password required, until it expires. That makes it the same class of secret as a session cookie. And yet the normal debugging reflex is to paste it into whatever decoder comes up first in a search — which, for a server-rendered decoder, means the token travels over the network to a stranger’s machine, where it will at minimum pass through request logs. That is a credential disclosure whether or not anyone acts on it, and in a regulated environment it is a reportable one.

This decoder is a static page: the parsing runs in your browser’s own JavaScript, and the token is never sent anywhere, logged, or stored. You do not have to take that on faith — open your browser’s network tab while you paste, or disconnect from the internet entirely and watch the tool keep working. There are no accounts here, no saving, and no server to send anything to.

The honest caveat is that a token which has already been shared elsewhere is already compromised, and this page cannot un-share it. Pasting a production token into a chat thread, a bug report, a screenshot, or a support ticket has the same effect as posting a password. Rotate or revoke it — and note that revocation is genuinely hard with stateless JWTs, because a signed token stays valid until exp unless you maintain a denylist keyed on jti or rotate the signing key itself. Short expiry times are the usual mitigation, which is precisely why the exp readout above is worth looking at when you are triaging a leak.

Frequently asked questions

Can this tool verify a JWT signature?

Yes. Paste the signing key into the Verify the signature panel and the check runs in your browser using the Web Crypto API — the shared secret for HS256, HS384 and HS512, or the issuer’s public key (PEM or JWK) for RS, PS, ES and EdDSA. The key is never uploaded. Without a key the tool only decodes, and a decoded payload proves nothing on its own. In production, verify server-side with a maintained library and pin the algorithm you expect rather than trusting the token’s own alg header.

Is a JWT encrypted, or can anyone read the payload?

A standard signed JWT is encoded, not encrypted. The header and payload are plain JSON in Base64URL, which anyone holding the token can reverse with no key at all, exactly as this page does. The signature makes tampering detectable, not the contents private. Never put passwords, API keys, card numbers or other secrets in a payload. If the contents must actually be hidden, you need a JWE, which has five segments and cannot be read without its decryption key.

Is it safe to paste a JWT into an online decoder?

It depends entirely on where the decoding happens. A server-side decoder receives your token over the network and will at minimum log it, which is a disclosure of a live credential. This decoder runs in your browser and sends nothing anywhere, which you can confirm by watching the network tab or by going offline and watching it still work. Even so, treat any production token you have pasted anywhere else as burned and rotate it.

Why does every JWT start with “eyJ”?

Because the header is a JSON object, so its first two characters are an opening brace and a double quote, and that pair Base64-encodes to eyJ every time. It is a handy signature when searching logs: a long dotted string beginning with eyJ is almost certainly a token that has leaked into somewhere it should not be. The decoder uses the same heuristic to tell you when a pasted string is probably not a JWT at all.

How do I tell whether a JWT has expired?

Read the exp claim, which is seconds since 1 January 1970 UTC, and compare it with the current time. The tool does that for you and shows the date in UTC alongside how long ago it passed or how long is left. Two cautions: a token with no exp claim never expires on its own, and a value that looks like milliseconds is a common issuer bug that produces an absurd future date, which the tool flags.

What is the alg none vulnerability?

The JWT spec permits an unsecured token whose alg is none and whose signature is empty. Libraries that read alg from the header to decide how to verify would faithfully perform no check at all, so an attacker could rewrite the payload, set alg to none, strip the signature and be accepted. The fix is to pin the expected algorithm on the server rather than trusting the header, and to reject none outright wherever a token is used as a credential.