Dev Tools
Client-sidefiles never upload

JWT decoder / generator

Decode JWT segments and inspect expiry; or re-sign with a secret. All computation runs locally in your browser.

Header
{
  "alg": "HS256",
  "typ": "JWT"
}
Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}
No exp claim
β€ΊHow to use
  1. Decode: paste the JWT on the left, the right side parses header / payload and labels Expired / Valid.
  2. Sign: switch to the Sign tab, edit the payload (JSON) + secret (HS256), the right side mints a new token live.
  3. Everything runs inside your browser β€” the secret and token never leave your machine.
Tips
  • Want to simulate an expired token? Set exp to a past Unix timestamp.
  • Only HS256 is supported right now. RS256 / ES256 are coming.

⚠️ Never paste production secrets into any online tool (including this one). Validate critical tokens in an isolated environment.

JWT in depth: three segments, HS256 vs RS256, common pitfalls, safe storage

JWTs are the most widely adopted stateless authentication standard of the past decade β€” and one of the most frequently misused protocols on the OWASP Top 10. From a QA angle, understanding the structure is what turns vague "test auth" tickets into concrete security test cases.

What the three segments are, and why

JWT splits on . into three Base64URL-encoded segments: - Header: {"alg":"HS256","typ":"JWT"} β€” algorithm and type - Payload: {"sub":"123","exp":1700000000,"role":"admin"} β€” claims (subject, expiry, custom fields) - Signature: the signed digest of header.payload using the algorithm in the header, with a secret (HS256) or private key (RS256) Header and payload are not encrypted β€” only encoded. Anyone can Base64-decode them and read the contents. Security comes from the signature: the verifier recomputes the signature with its secret / public key, compares with the token's signature, and rejects anything that doesn't match β€” so any tampering with the payload immediately fails.

HS256 vs RS256 vs ES256: which to use when

- HS256 (symmetric): fast and simple, but signer and verifier share the same secret. Fits inside a single service (API gateway β†’ your own backend). - RS256 (asymmetric RSA): sign with a private key, verify with a public key. Fits one-to-many (auth server issues tokens, many microservices verify). Public keys can be shared safely. - ES256 (asymmetric ECDSA): same model as RS256 but signatures are shorter and signing is ~80% faster. Recommended for new projects. WebCrypto and Node's standard library support all three natively.

Three vulnerabilities you must test

1. The alg: none attack: some older libraries accept {"alg":"none"} meaning "skip signature verification". Attackers tamper with the payload, leave the signature empty, and the server lets them through. 2. Algorithm confusion (RS256 β†’ HS256): the server expects RS256, but the attacker rewrites the header to alg: HS256 and signs with the public key as the HMAC secret. Libraries that don't enforce algorithm consistency accept it β€” and the public key is, well, public. 3. Missing exp / aud validation: most QA teams only check the signature is well-formed but forget: - Whether exp is in the past - Whether aud matches this service (prevents token reuse across services) - Whether iss is the trusted issuer Each of those needs its own negative test case in your suite.

Where to store tokens client-side (pick one risk)

- localStorage: easiest, but a single XSS leaks every token (malicious scripts read it directly). - httpOnly + Secure + SameSite=Strict cookies: immune to XSS, but vulnerable to CSRF unless you also use a CSRF token or SameSite=Lax with double-submit. - In-memory (React state): most secure, but refresh wipes it β€” you need a refresh token (in an httpOnly cookie) to mint a new one. The industry default: access token in memory + refresh token in httpOnly cookie. This combination has reasonable defenses against both XSS and CSRF.

JWT test checklist for QA

Positive: - Valid token β†’ 200 - The role / scope in the payload actually affects what the API returns Negative (skipping these means your system has a real hole): - Expired token β†’ 401 - Tampered signature β†’ 401 - alg: none header β†’ 401 (NOT 200) - Token issued for service A, sent to service B β†’ 401 (aud enforcement) - HS256 token signed with the RS256 public key β†’ 401 (algorithm confusion defense) - Payload role changed from user to admin (signature unchanged) β†’ 401 The [JWT decoder / generator tool](/en/tools/jwt) can manually craft every one of those cases.

Try it: use the [JWT tool](/en/tools/jwt)'s Sign mode to mint a token with alg: none or with exp in the past, then send it to your API and confirm it gets rejected.

Related tools