DEV Community

Snappy Tools
Snappy Tools

Posted on

JWT Decoding: Reading Tokens Without a Library

JSON Web Tokens (JWTs) are everywhere: authentication headers, session cookies, API keys. When debugging, you often need to see what is inside a token — expiry time, user ID, permissions, issuer. You do not need a library for this. JWTs are Base64-encoded JSON, and you can decode them in one line.

What a JWT actually contains

A JWT has three parts, separated by dots:

header.payload.signature
Enter fullscreen mode Exit fullscreen mode

Each part is Base64url-encoded. The header identifies the algorithm used to sign the token. The payload contains the claims — data the token asserts. The signature is a cryptographic hash that verifies the token has not been tampered with.

Typical decoded header:

{
  "alg": "HS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

Typical decoded payload:

{
  "sub": "user_12345",
  "email": "user@example.com",
  "iat": 1717200000,
  "exp": 1717286400,
  "roles": ["user", "admin"]
}
Enter fullscreen mode Exit fullscreen mode

Decoding without a library

The payload is just Base64url-encoded JSON. You can decode it in any language:

JavaScript (browser or Node.js):

function decodeJwt(token) {
  const payload = token.split('.')[1];
  // Base64url → Base64 → JSON
  const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
  return JSON.parse(atob(base64));
}
Enter fullscreen mode Exit fullscreen mode

Python:

import base64, json

def decode_jwt(token):
    payload = token.split('.')[1]
    # Add padding if needed
    padding = 4 - len(payload) % 4
    payload += '=' * (padding % 4)
    return json.loads(base64.urlsafe_b64decode(payload))
Enter fullscreen mode Exit fullscreen mode

Go:

import (
    "encoding/base64"
    "encoding/json"
    "strings"
)

func decodeJwt(token string) (map[string]interface{}, error) {
    parts := strings.Split(token, ".")
    payload, err := base64.RawURLEncoding.DecodeString(parts[1])
    if err != nil {
        return nil, err
    }
    var claims map[string]interface{}
    json.Unmarshal(payload, &claims)
    return claims, nil
}
Enter fullscreen mode Exit fullscreen mode

Note: decoding the payload gives you the data, but it does not verify the signature. Never trust the claims in a JWT until the signature is verified with the correct secret or public key.

What the claims mean

Common JWT claims (registered in RFC 7519):

Claim Name Type Meaning
iss Issuer string Who created the token
sub Subject string Who the token refers to (usually user ID)
aud Audience string/array Who the token is intended for
exp Expiry Unix timestamp When the token expires
iat Issued at Unix timestamp When the token was created
nbf Not before Unix timestamp Earliest time the token is valid
jti JWT ID string Unique identifier for this token

The exp and iat values are Unix timestamps (seconds since 1970-01-01 UTC). To convert them to a readable date, use a Unix timestamp converter.

Verifying vs. decoding

Decoding is read-only. Any client can decode a JWT — the payload is not encrypted, just encoded. If your application needs to act on the claims (check user roles, validate session), it must verify the signature first.

For HS256 tokens, verification requires the shared secret. For RS256 tokens (common in OAuth 2.0 and OIDC), verification uses the public key from the server's JWKS endpoint.

In Node.js:

const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// throws if signature is invalid or token is expired
Enter fullscreen mode Exit fullscreen mode

Never skip verification in production. A decoded-but-unverified JWT can be forged — the server must check that the signature matches.

Debugging JWT issues

Common problems and how to diagnose them:

"Token expired" — decode the payload and check exp. Convert the Unix timestamp: if exp is in the past, the token is expired. Check whether the clock on your server is in sync (NTP drift can cause token rejections even for freshly issued tokens).

"Invalid audience" — check aud in the payload. It must match what your application expects. Some providers issue tokens for one service that other services refuse.

"Signature verification failed" — the token was signed with a different key, or the token body was modified after signing. Check that your application is using the correct key or JWKS endpoint.

Unexpected claims — decode and inspect the full payload. Sometimes tokens contain custom claims (namespaced like https://myapp.com/roles) that are not obvious from the standard field names.

Quick token inspection

For debugging during development, the JWT Decoder on SnappyTools gives you a formatted view of the header and payload with timestamps converted to readable dates. Paste the token and see all claims instantly — nothing is sent to any server.


Dealing with a JWT in production debugging? Decode it instantly here — completely client-side, safe to use with real tokens.

Top comments (0)