logo
GeekFormat

JWT Decode, Verify & Generate

Encoded Token172 charsUpdated 2026-07
header
payload
signature
Valid JWTHS256
Secret38 chars
Header
{
  "alg": "HS256",
  "typ": "JWT"
}
Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}
Claims details4 items
sub· Subject1234567890
name· NameJohn Doe
admin· Admintrue
iat· Issued at151623902201/18/2018, 09:30:22
Header details2 items
algHS256HMAC (symmetric)
typ· TypeJWT
All operations run locally in your browser; the Token and key are never sent to any server.

Free online JWT workbench: decode, verify and generate JSON Web Tokens. Supports 13 algorithms (HS256 / RS256 / ES256 / PS256 / EdDSA, etc.). Both PEM and JWK keys. Auto key-pair generation. exp/iat/nbf parsed automatically. 100% client-side — token never leaves your browser.

Related

Use Cases

  • Decode JWT online: quickly split a token during OAuth 2.0 / OIDC login debugging to inspect Header algorithm and Payload Claims
  • Verify JWT signatures: use a Secret / public key to validate token signatures in real time and decide whether a 401 is 'bad signature', 'expired', or 'algorithm mismatch'
  • Generate test JWTs during integration: issue a JWT in the browser with a test key pair, no need to ask the backend to temporarily change code to mint a token
  • PEM ↔ JWK conversion: the OIDC IdP gave you JWK but the server needs PEM (or vice versa) — convert in place instead of writing an OpenSSL script
  • Diagnose 401 Unauthorized: step through expiry, alg-vs-gateway match, key match, and PEM formatting one at a time
  • Compare RS256 vs PS256: many OIDC providers default to RS256 while AWS SigV4 prefers PS256 — switch algorithms with the same RSA key pair and compare results
  • Learn JWT internals: change the alg, edit the Payload, swap the key in the browser and watch the signature break in real time — far faster than reading the RFC

Features

  • Decode + Verify + Generate in one place: paste a token to split Header/Payload/Signature, switch algorithm and key to verify the signature, edit Claims and re-sign to produce a new token — covers the full JWT troubleshooting chain
  • All 13 algorithms supported: HS256/HS384/HS512 (HMAC), RS256/RS384/RS512 (RSA-PKCS1-v1_5), ES256/ES384/ES512 (ECDSA), PS256/PS384/PS512 (RSA-PSS), EdDSA — covers OAuth 2.0, OIDC, JWS, API gateways and enterprise SSO
  • PEM and JWK formats: RSA, ECDSA and Ed25519 keys accept PEM strings (with -----BEGIN PUBLIC KEY----- headers) and JWK JSON (kid/kty/n/e fields) — drop in an OIDC /.well-known/jwks.json output directly
  • Multiple HMAC secret input forms: raw UTF-8 string, hex bytes, or Base64/Base64URL-encoded secret — switch with one click, so 'looks the same but signature fails' never happens
  • Auto key pair generation: RSA-2048, RSA-4096, P-256, P-384 and Ed25519 key pairs generated in-browser, presented in both PEM and JWK formats for immediate test use
  • Semantic Claims highlighting: exp / iat / nbf auto-converted to human-readable time with expired / not-yet-valid / issued status highlighted; alg / typ / kid in Header labeled individually
  • 100% client-side, zero upload: every parse, verify and sign runs through the browser's native Web Crypto API — tokens, keys and payload never leave the browser, matching the security principle of 'never paste production tokens into random online tools'
  • One-click copy: Header, Payload, Signature and the newly generated token are each independently copyable for documentation, tickets and curl commands

How to Use

  1. Paste or type the token: drop the JWT string into the input box; the tool automatically splits Header, Payload and Signature and pretty-prints each as JSON
  2. Select the algorithm: switch among HS256 / RS256 / ES256 / PS256 / EdDSA; the tool infers from Header.alg and flags any mismatch
  3. Import a key: paste an HMAC secret, a PEM string, or a JWK JSON; for asymmetric algorithms, click 'Generate key pair' to get a test key immediately
  4. Verify or re-sign: in Decode mode verify with a public key and see '✅ signature valid / ❌ signature invalid'; in Encode mode edit Claims and sign with a private key to produce a new token

FAQ

What are the three parts of a JWT?

A JWT string has the form `Header.Payload.Signature`, with each segment Base64URL-encoded. The Header declares the algorithm (e.g. HS256, RS256) and the token type (typ: JWT). The Payload carries user Claims — common fields include sub (user ID), iat (issued at), exp (expiration), nbf (not before), aud (audience), and iss (issuer). The Signature is a key-based signature over the first two segments; it provides tamper-evidence, not confidentiality.

Can I edit the Payload after decoding a JWT?

You can view and edit it, but you **cannot forge** a valid token. The moment you change Header or Payload, the original Signature becomes invalid and the server will reject the token. To make a modified token valid again, you must re-sign it with the same algorithm and key — which is exactly why this tool offers decode + edit + generate in one place: inspect once, edit, and immediately mint a new token for testing, without bothering the backend each time.

What is the difference between HS256, RS256, ES256, PS256 and EdDSA?

HS256 is HMAC + SHA-256, symmetric (the same Secret signs and verifies), simple and fast. RS256 is RSASSA-PKCS1-v1_5 + SHA-256, asymmetric (private key signs, public key verifies), the most common OIDC algorithm. PS256 is RSA-PSS, a security upgrade of RS256, preferred by AWS SigV4 and Cognito. ES256 is ECDSA + P-256, short signatures and good performance, used by Apple Sign in with Apple. EdDSA (Ed25519) is a next-generation algorithm with deterministic and very fast signatures, recommended by OAuth 2.1. This tool supports all 13 algorithms and lets you switch between them in the UI.

Why does my JWT always show 'Invalid Signature'?

The five most common causes: (1) key mismatch — the server's Secret or public key is not byte-for-byte identical to what you pasted (watch for PEM headers, blank lines, trailing spaces); (2) Base64 vs Base64URL mix-up — JWT must use Base64URL (`+` → `-`, `/` → `_`, no `=`), standard Base64 will compute a different signature; (3) wrong algorithm — Header says HS256 but the server verifies as RS256; (4) the Secret is being decoded twice (e.g. once as Hex, once as Base64) — this tool lets you pin the Secret input form to UTF-8 / Hex / Base64 to prevent double decoding; (5) clock skew on the server makes iat appear to be in the future and the token is treated as not-yet-valid.

Can I use the same RSA key to cross-verify RS256 and PS256 tokens?

**No.** RS256 uses RSASSA-PKCS1-v1_5, PS256 uses RSA-PSS — different padding, different signature lengths, different verification logic. A token signed by the same RSA key with RS256 will always fail PS256 verification, and the signature may be a few bytes longer. Always make sure the signer and the verifier use the same algorithm. RSA-PSS also has a `saltLength` parameter — OpenSSL defaults to 32, Node jose to 32, Go's `rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}` is the equivalent. Lock this down before any cross-language integration test.

What is an alg: none attack and how do I prevent it?

The alg: none attack is the most famous JWT vulnerability (CVE-2015-9235 and others). An attacker rewrites the token Header to `{"alg": "none"}` and empties the Signature segment. If the server then picks a 'none algorithm' from the header, the token sails through. A related variant rewrites an RS256 token to HS256 and signs it with the public key as if it were a shared secret. **There is only one defense**: hard-code an algorithm whitelist on the server — for example `algorithms: ['RS256']` — and never let the token tell the server which algorithm to use. This tool's UI shows both Header.alg and the algorithm you actually selected, and highlights mismatches — a live demo of 'the client must not trust Header.alg'.

How do I convert between PEM and JWK, and which is more common?

PEM is the Base64 string wrapped with `-----BEGIN PUBLIC KEY-----` headers and is most common in traditional OpenSSL / Java / Go stacks. JWK is a JSON structure (with `kty`, `n`, `e`, `kid`, `alg` fields) and is the OAuth 2.0 / OIDC standard. OIDC IdPs generally publish public keys as JWKs at the `/.well-known/jwks.json` endpoint; servers consume them via JOSE libraries (jose, PyJWT, jsonwebtoken). You can convert between them online — this tool's RSA / ECDSA / Ed25519 section accepts either PEM or JWK on input and shows the equivalent representation on the other side.

Can I verify a JWT signature directly in the browser?

Yes. The browser's native Web Crypto API (`window.crypto.subtle`) supports HMAC, RSA, ECDSA, RSA-PSS and Ed25519 signing and verification, with no third-party libraries needed. This tool uses `crypto.subtle.importKey` and `crypto.subtle.verify` on the front end for every verification, which is why it never uploads your tokens or keys. Note that Web Crypto is only available in secure contexts: **HTTPS** or `localhost`.

Is my token sent to a server?

No. All parsing, verification and signing in this tool runs in the browser via the native Web Crypto API. The token, Header, Payload, Signature, Secret / private key, and any generated key pairs are never sent to a server. Open the browser DevTools Network tab and you will see no outbound request carrying token content. The page can be used offline once it has loaded.

Is JWT a safe place to put user-sensitive information?

**No.** JWT Payload is plaintext by default — anyone with the token can Base64URL-decode it and read its content. There is no encryption. **Never** put passwords, national ID numbers, credit card numbers, API keys, access tokens or refresh tokens in plaintext into a JWT. If you need encryption, use JWE (RFC 7516) instead of JWS. JWTs are appropriate for non-sensitive Claims (user ID, role, expiration, tenant ID); sensitive data should be looked up server-side using `sub`.

What is JWT Decode, Verify & Generate?

JWT (JSON Web Token) is an open standard defined by the IETF in RFC 7519 (RFC 7515 describes the JWS signing form, RFC 7516 describes JWE encryption, RFC 7517 defines JWK keys) for securely transferring 'asserted' user information between HTTP requests, OIDC flows and microservice calls. A standard JWT string is composed of three Base64URL-encoded segments: Header (algorithm and type), Payload (Claims, the user data) and Signature (a key-based signature over the first two). The three segments are joined by a literal dot `.`, e.g. `eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c`.

**JWT is not encryption.** This is the most common misconception. The Payload is plaintext by default — anyone can Base64URL-decode it and read the contents. JWT provides **tamper-evidence**, not confidentiality: the server re-signs Header.Payload with the shared key and compares to the token's Signature. If they match, the token has not been altered in transit. This is the 'train ticket with an anti-counterfeit stamp' metaphor: the inspector cares whether the ticket is genuine, not whether the QR code is unreadable.

JWT cleanly divides responsibilities across 13 algorithms. **HMAC family** (HS256/HS384/HS512) uses a symmetric key for both signing and verifying, fast and simple, suitable for a single service or a trusted cluster; the secret must be at least the digest length (e.g. ≥ 32 bytes for HS256). **RSA family** (RS256/RS384/RS512) uses RSASSA-PKCS1-v1_5, the most common asymmetric scheme — signers hold the private key, verifiers hold the public key. **RSA-PSS family** (PS256/PS384/PS512) uses the newer RSA-PSS padding with stronger security guarantees, preferred by AWS SigV4 and modern OIDC identity providers. **ECDSA family** (ES256/ES384/ES512) uses elliptic curves (P-256/P-384/P-521 respectively) with shorter signatures and better performance. **EdDSA** (mainly Ed25519) is extremely fast and deterministic (same message + same key = same signature every time) and is the recommended algorithm in OAuth 2.1 and new protocols.

Security is where JWT trips up in production. The OWASP JWT Cheat Sheet calls out at least four hard rules: (1) never put passwords, national IDs, card numbers or API keys as plaintext into the Payload; (2) the server must **never trust the alg field** declared in the Token Header — it must verify with a hard-coded algorithm, otherwise an attacker rewriting the header to `alg: none` bypasses everything (this is the root of historical CVEs such as CVE-2015-9235); (3) HMAC secrets must be random and at least 32 bytes, never short strings; (4) verification is more than signature checks — you must also validate `exp` (expiration), `nbf` (not-before), `iss` (issuer) and `aud` (audience). This tool highlights every one of these Claims in the UI so you can tell at a glance whether a failure is a signature problem, a timing problem, or a Claims problem.

JWT is not a replacement for sessions. Sessions store user state on the server (Redis or a database); JWT packs the state into the token. Microservice architectures, stateless APIs, mobile clients and CORS-heavy setups benefit from JWT; traditional enterprise systems and flows that require instant revocation (e.g. 'kick this user out now') are still better served by sessions. This tool covers both pure JWT debugging and the Token-parsing / signature-verifying / Payload-editing steps of a session-to-JWT migration.

Code Examples

50 lines of Node.js: hand-written HS256 sign and verify

javascript

Distills JWT's core mechanism: encode Header and Payload in Base64URL, then HMAC-SHA256 the `header.payload` string. In production, use a library like jsonwebtoken or jose — this snippet exists to make signature failures debuggable.

const crypto = require('node:crypto')

function b64url(input) {
  return Buffer.from(input)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '')
}

function hmacSign(data, secret) {
  return b64url(crypto.createHmac('sha256', secret).update(data).digest())
}

function sign(payload, secret) {
  const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
  const body = b64url(JSON.stringify(payload))
  const signature = hmacSign(`${header}.${body}`, secret)
  return `${header}.${body}.${signature}`
}

function verify(token, secret) {
  const [h, p, s] = token.split('.')
  const expected = hmacSign(`${h}.${p}`, secret)
  const a = Buffer.from(s)
  const b = Buffer.from(expected)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

const SECRET = 'my-super-secret-key'
const payload = { userId: 42, role: 'admin', exp: Math.floor(Date.now() / 1000) + 3600 }
const token = sign(payload, SECRET)

console.log(token)
console.log(verify(token, SECRET))       // true
console.log(verify(token, 'wrong-key'))  // false

Browser-side: Web Crypto API for HMAC signing

html

The core idea of this tool's front-end: use the browser's native crypto.subtle for HMAC, RSA, ECDSA, RSA-PSS and Ed25519 verification — no third-party library needed. Drop this into any HTML page to mint HS256 tokens.

<!doctype html>
<html>
  <body>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById('out')

      const b64url = buf =>
        btoa(String.fromCharCode(...new Uint8Array(buf)))
          .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

      const b64urlStr = str => b64url(new TextEncoder().encode(str))

      async function sign(payload, secret) {
        const header = b64urlStr(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
        const body = b64urlStr(JSON.stringify(payload))
        const data = new TextEncoder().encode(`${header}.${body}`)

        const key = await crypto.subtle.importKey(
          'raw',
          new TextEncoder().encode(secret),
          { name: 'HMAC', hash: 'SHA-256' },
          false,
          ['sign']
        )

        const sig = await crypto.subtle.sign('HMAC', key, data)
        return `${header}.${body}.${b64url(sig)}`
      }

      ;(async () => {
        const token = await sign({ user: 'alice', role: 'admin' }, 'browser-demo-secret')
        out.textContent = token
      })()
    </script>
  </body>
</html>

Node.js: verify RS256 with jose (the right way)

javascript

Use a mature library like jose, jsonwebtoken, or PyJWT in production — never roll your own. This snippet shows how jose verifies an RS256 token, prints the failure reason on error (alg mismatch, bad signature, expired, kid not found, etc).

import { jwtVerify, importSPKI } from 'jose'
import { readFile } from 'node:fs/promises'

const token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.payload.signature'
const publicKeyPem = await readFile('public.pem', 'utf8')

try {
  const { payload, protectedHeader } = await jwtVerify(
    token,
    await importSPKI(publicKeyPem, 'RS256'),
    {
      issuer: 'https://idp.example.com',
      audience: 'my-app',
      algorithms: ['RS256'],     // critical: algorithm whitelist blocks alg:none and HS256-substitution attacks
    }
  )
  console.log('alg =', protectedHeader.alg)
  console.log('sub  =', payload.sub)
} catch (err) {
  console.error('verify failed:', err.code, err.message)
  // common: ERR_JWT_EXPIRED / ERR_JWS_INVALID / ERR_JWS_SIGNATURE_VERIFICATION_FAILED
}

Python: parse a token and print its Claims with PyJWT

python

The most common way to debug JWT on the Python side. PyJWT's decode() validates exp / nbf / iat by default and returns the result as a plain dict.

import jwt

token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.SflKxw'

# Parse (no signature check, just look at Header and Payload)
print(jwt.get_unverified_header(token))
# {'alg': 'HS256', 'typ': 'JWT'}

print(jwt.decode(token, options={'verify_signature': False}))
# {'sub': '1234'}

# Full verification
claims = jwt.decode(
    token,
    'my-super-secret-key',
    algorithms=['HS256'],
    audience='my-app',
    issuer='my-service',
)
print(claims['sub'])

Supported Video Formats

FormatMIMEBrowser supportWhen to use
HS256 / HS384 / HS512HMAC + SHA-256/384/512UniversalSymmetric-key algorithm. The same Secret signs and verifies, simple and fast, suitable for service clusters inside a trusted boundary. Secret must be at least 32 random bytes.
RS256 / RS384 / RS512RSASSA-PKCS1-v1_5 + SHA-2Web Crypto APIThe most common asymmetric algorithm. Sign with the private key, verify with the public key. Default for most OIDC IdPs, API gateways and enterprise SSO.
PS256 / PS384 / PS512RSA-PSS + SHA-2Web Crypto APIRSA-PSS is the updated, more secure replacement for RS256. AWS SigV4, AWS Cognito and modern OIDC providers tend to prefer it. saltLength must be specified explicitly.
ES256 / ES384 / ES512ECDSA + P-256/P-384/P-521Web Crypto APIElliptic-curve signatures. Short signatures (ES256 is only 64 bytes), fast performance, smaller JWTs. Apple Sign in with Apple uses ES256 by default.
EdDSA (Ed25519)Ed25519 / Ed448Web Crypto APINext-generation high-performance signature algorithm. Deterministic signatures (same message + same key always produce the same result), no nonce-reuse risk, recommended by OAuth 2.1.

Output Example

A real MP3 file encoded to a Data URI — copy-ready:

Header (Base64URL):  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload(Base64URL):  eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNzE2MjM5MDIyfQ
Signature:           kZJfaYjK3iCkVFL5EL9zGRZ5SmD8_x2h6B5c7pVFfVGo

Header decoded:
  {
    "alg": "HS256",
    "typ": "JWT"
  }

Payload decoded:
  {
    "sub": "1234567890",
    "name": "Jane Doe",
    "iat": 1716239022        // 2024-05-20 14:23:42 UTC
  }

Privacy & Security

This JWT workbench runs 100% in your browser, backed by the browser's native Web Crypto API. The token you paste, the Header, the Payload, the Signature, the HMAC Secret, any RSA/ECDSA private keys, any generated key pairs, and every signature/verify computation stay on your device — they never travel to a server, are never logged, never analyzed, and never cached. The page can be used offline once loaded. Best practice: never paste long-lived production tokens or production private keys into any online tool, including this one. This tool is appropriate for debugging, integration testing, learning, and minting test tokens — production secrets and private keys should be handled in a controlled, local environment.

Authoritative References

Troubleshooting

Invalid Signature: 'signature is invalid'

The token and the verification key do not match. The usual causes: (1) the server's key and the key you pasted into the tool are different (most common); (2) the key contains invisible characters (trailing space, newline, zero-width char); (3) Base64 and Base64URL are mixed; (4) the wrong algorithm is selected (Header says HS256, server verifies as RS256, or vice versa). First check that the key matches byte-for-byte: pay attention to PEM trailing newlines, HMAC secret trailing spaces, and JWK `k` values that may have been wrongly URL-decoded. Confirm the algorithm in the tool matches the token's Header.alg. Use this tool's decode-plus-verify to reproduce the exact error and confirm it happens between 'decode succeeded' and 'verify failed'.

HTTP 401 Unauthorized: Token Expired or iat in the future

Signature verification passed, but `exp` is in the past (Token Expired), `nbf` is still in the future (Not Before), or `iat` is later than the server's current time (often due to clock skew). Another common case is a mismatched `aud` (audience): the token was issued for App A but validated by App B. Use the highlighted Claims area in this tool to spot exp / nbf / iat status at a glance. Red exp = expired, please re-issue. Red nbf = not yet valid, check if iat is a future time. Red aud = wrong audience, check that the server has the right audience configured.

PEM or JWK import error

PEM has extra blank lines or missing header/footer markers (`-----BEGIN PUBLIC KEY-----`); JWK is missing required fields (`kty` / `n` / `e` / `kid`); JWK `k` value missing Base64URL padding (must be `=`-padded or strictly `=`-stripped); an RSA private key is being imported as a public key; an Ed25519 private key (32-byte seed) is being supplied as 64 bytes. Re-export a standard PKIX PEM with `openssl rsa -in key.pem -pubout -outform PEM` or `openssl ec -in key.pem -pubout`; for JWK, use the raw output of the OIDC Discovery `/.well-known/jwks.json` endpoint rather than hand-rolling the encoding. This tool auto-detects PEM headers, missing padding, and missing JWK fields and surfaces a hint.

RS256 vs PS256 cross-language signature failures

RS256 uses RSASSA-PKCS1-v1_5, PS256 uses RSA-PSS. The two **cannot cross-verify** — a token signed with RS256 by a given RSA key will always fail PS256 verification, and the signature may differ in length by a few bytes. Make sure the signer and the verifier use the same algorithm. AWS SigV4 uses PS256, most OIDC IdPs use RS256. RSA-PSS also has a `saltLength` parameter — OpenSSL defaults to 32, jose to 32, Go's `rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}` is the right equivalent. Lock this down before cross-language integration testing.

alg: none attack and HS256-key-substitution attack

The server picks a verification algorithm based on the alg declared in the token's Header. An attacker rewrites the header to `alg: none` (no signature) or rewrites an RS256 token to HS256 (using the public key as if it were a shared HMAC secret) and bypasses verification. Historical CVEs such as CVE-2015-9235 (jsonwebtoken) and CVE-2022-23529 (multiple Node libraries) trace back to this pattern. The server must enforce an algorithm whitelist — for example `algorithms: ['RS256']` — and must never let the token tell the server which algorithm to use. This tool's UI shows both Header.alg and the algorithm you actually selected, and flags mismatches: that is itself a live demo of 'the client must not trust Header.alg'.