jwt authentication security api

How to Decode a JWT: A Complete Guide to Reading and Verifying JSON Web Tokens

Everything you need to understand a JWT — header, payload, claims, expiry, signature verification, and the security warnings you should pay attention to.

· ByteKiln

JWT (JSON Web Token) is everywhere in modern authentication. Every OAuth flow, most REST APIs, and nearly all SPA auth systems use JWTs. But the token itself is just a Base64URL-encoded blob — three parts separated by dots. Without a decoder, you can’t read it.

This guide covers what’s inside a JWT, how to read each part, what the claims mean, and how signature verification works in the browser.


The Structure of a JWT

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbImFkbWluIl0sImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDg2NDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three Base64URL-encoded segments separated by . (for a deep dive into the encoding format itself, see the Base64 guide):

<header>.<payload>.<signature>

The header describes the token format and algorithm:

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

alg is the signature algorithm. typ is always JWT for a standard JWT.

Common algorithm values:

AlgorithmTypeNotes
HS256HMAC-SHA256Symmetric; requires shared secret
HS384HMAC-SHA384Symmetric; stronger than HS256
HS512HMAC-SHA512Symmetric; strongest HMAC
RS256RSA-SHA256Asymmetric; uses public/private key pair
RS384RSA-SHA384Asymmetric
RS512RSA-SHA512Asymmetric
ES256ECDSA-SHA256Asymmetric; smaller keys than RSA
PS256RSASSA-PSSAsymmetric; modern RSA variant
noneNo signatureSecurity risk — see below

Payload

The payload contains the claims — statements about the token subject and additional metadata:

{
  "sub": "user_123",
  "name": "Alice",
  "roles": ["admin"],
  "iat": 1700000000,
  "exp": 1700086400
}

Signature

The signature is not JSON — it’s a binary cryptographic signature, Base64URL-encoded. It proves the header and payload weren’t tampered with (for signed tokens) or is empty (for alg: none).


Standard Claims

The JWT spec defines a set of registered claims. These have specific meaning:

ClaimNameTypeDescription
subSubjectstringWho the token is about (typically a user ID)
issIssuerstringWho created the token (e.g., your auth server URL)
audAudiencestring/arrayIntended recipients
expExpirationUnix timestampWhen the token expires
iatIssued AtUnix timestampWhen the token was created
nbfNot BeforeUnix timestampToken not valid before this time
jtiJWT IDstringUnique identifier for the token

The decoder renders timestamps as human-readable dates. exp: 1700086400 becomes something like Tuesday, Nov 15, 2023 at 20:00:00 UTC — immediately readable.


Reading the Expiry

The exp claim is the most commonly inspected claim. The decoder shows:

  • The formatted date and time
  • Whether the token is currently valid or expired

This is useful when debugging authentication failures — a common cause is simply that a token has expired and the refresh flow failed silently.

The decoder also shows iat (issued at) and nbf (not before) as formatted timestamps.


Security Warnings the Decoder Shows

The decoder highlights three specific security issues:

1. alg: none

The none algorithm means no signature is applied. The token is not authenticated — anyone can create a none token with any claims and it will pass format validation.

This was a real CVE (CVE-2015-9235) — some JWT libraries accepted alg: none tokens even in contexts that should require a signature. If you see this in a real token your application is accepting, it’s a critical bug.

2. Missing exp

A JWT without an expiry never expires. If an attacker gets hold of such a token, they have permanent access. Legitimate tokens should always include exp.

3. Expired token

The token has an exp claim but the time has passed. In some authentication systems, expired tokens should be handled gracefully (refresh the token), but accepting an expired token without error is a bug.


Signature Verification: HMAC

For HMAC tokens (HS256, HS384, HS512), the decoder can verify the signature if you provide the secret. For generating and verifying HMACs independently — useful for webhook payload verification (GitHub, Stripe, Shopify) and API request signing — the Hash Generator guide includes a dedicated HMAC tab for SHA-256, SHA-384, and SHA-512.

How HMAC signing works:

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

If you have the same secret that signed the token, you can verify that the header and payload haven’t been tampered with.

Where to get the secret:

  • For tokens from your own application, the secret is in your config (usually an environment variable)
  • For third-party tokens (OAuth, identity providers), you won’t have the HMAC secret — those typically use RS256 or ES256 with public keys

Enter the secret in the Signature panel. The decoder uses the SubtleCrypto API to compute the HMAC in-browser and compares it to the token’s signature. No data leaves your machine.

Result states:

  • Valid — signature matches; the token was signed with this secret and hasn’t been tampered with
  • Invalid — signature doesn’t match; either wrong secret or the token was modified
  • ⚠️ Unsupported — the algorithm can’t be verified in this mode (RS256, ES256, etc.)
  • 🔴 Error — malformed token or key

Signature Verification: RSA and ECDSA

For asymmetric algorithms (RS256, RS384, RS512, ES256, ES384, ES512, PS256), verification requires the public key from the issuer — not the private key.

Public keys are commonly distributed via JWKS (JSON Web Key Set) endpoints:

https://your-auth-server.com/.well-known/jwks.json

A JWKS response looks like:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "abc123",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2...",
      "e": "AQAB"
    }
  ]
}

The decoder supports pasting either:

  • A single JWK (the key object)
  • A full JWKS (the { "keys": [...] } object) — it finds the matching key by kid (key ID) from the token header

This lets you verify tokens from Google, Auth0, Okta, Keycloak, and any other OIDC-compliant provider — entirely in-browser.


The Claims Inspector

The decoder renders a table of claims with human-readable descriptions for known fields:

Raw claimFriendly labelExample rendered value
subSubjectuser_123
expExpires AtThu Mar 12 2026 09:00:00 UTC
iatIssued AtThu Mar 12 2026 08:00:00 UTC
nbfNot BeforeThu Mar 12 2026 08:00:00 UTC
issIssuerhttps://auth.example.com
audAudienceapi.example.com
jtiJWT IDabc-123-def

Custom claims (anything outside the registered set) are displayed as-is.

Arrays in claims (like roles: ["admin", "editor"]) are rendered as badge pills for readability.


Practical Workflows

Debugging an authentication failure

User reports they’re getting 401 errors. You grab their token from the request header and paste it in:

  • Check exp — is it expired?
  • Check iss — is it from the right issuer?
  • Check aud — does it match the API?
  • Verify the signature if you have the secret — was the token tampered with?

Most auth failures fall into these categories.

Understanding a third-party token

You’re integrating with a new OAuth provider and you want to understand what’s in the token before you write code to consume it. Decode it, read the claims, understand the structure.

Checking token freshness

Before making an API call in a long-running script, decode the token and check if exp is within the next few minutes. If so, refresh it first.

Security audit

You’re reviewing an old authentication system and someone mentions tokens don’t expire. Decode a sample token — if there’s no exp claim, you’ve confirmed the problem.


What the Decoder Doesn’t Do

Encrypt or decrypt. JWTs can be encrypted (JWE — JSON Web Encryption), which is different from signed JWTs (JWS). Encrypted JWTs look like 5-part tokens. The decoder handles signed JWTs (JWS), not encrypted ones.

Validate claims semantically. It tells you that exp is in the past, but it doesn’t know if iss matches what your application expects, or if the aud is correct for your service. Claim validation is your application’s job.

Store or transmit your tokens. The decoder runs entirely in-browser. Nothing is sent to any server.


Decoding a JWT takes three seconds with the right tool. The value is being able to read the claims, understand the expiry, and check the signature — all in your browser, all without any server roundtrip.