How to Decode JWT Tokens Safely (Step-by-Step Guide)

25 Jan 2026 1,754 words

How to Decode JWT Tokens Safely

JWT (JSON Web Token) is a compact, URL-safe token format defined by RFC 7519. It is used for authentication and information exchange between parties. JWTs have become the de facto standard for representing claims securely between two parties, such as a client and a server in a modern web application. They are self-contained, meaning all the information needed to authenticate a request is embedded directly in the token itself, eliminating the need for server-side session storage in many architectures.

The popularity of JWTs spans across single-page applications, mobile apps, API gateways, and microservices communication. When a user logs into an application, the server issues a JWT that the client stores and presents with each subsequent request. The server then verifies the token's signature to authenticate and authorize the request. Understanding how to decode and inspect JWTs safely is critical for developers who need to debug authentication flows, verify claims, and troubleshoot token-related issues without exposing sensitive data or introducing vulnerabilities.

Understanding JWT Structure

A JWT consists of three parts separated by dots, each encoded using Base64URL (a URL-safe variant of Base64 encoding):

header.payload.signature

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwi
bmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4
fwpMeJf36POk6yJV_adQssw5c

The first segment is the header, which contains metadata about the token including the signing algorithm and token type. The second segment is the payload, which carries the claims or assertions being made. The third segment is the signature, which is computed by taking the encoded header and payload, combining them with a dot, and signing the result using the algorithm specified in the header along with a secret key or private key.

Base64URL Encoding Detail

JWT uses Base64URL encoding, which differs from standard Base64 in two important ways. First, it replaces the + character with - to avoid issues in URL query parameters where + is interpreted as a space. Second, it replaces the / character with _ to avoid conflicts with URL path separators. Additionally, padding characters (=) are stripped from the end of JWT segments because they are unnecessary for decoding and would only add unnecessary bytes to the token.

JWT Header Claims

The header typically contains the algorithm and token type, but it can also include additional metadata for advanced scenarios.

Claim Name Example Values
alg Algorithm HS256, RS256, ES256
typ Token Type JWT
kid Key ID unique-key-id
jku JWK Set URL https://example.com/.well-known/jwks.json

The kid (Key ID) claim is particularly important in environments where multiple signing keys are in rotation. It allows the server to look up the correct key from a key set without guessing. The jku claim points to a URL containing a JSON Web Key Set (JWKS), which the verifier can fetch to obtain the public key for signature verification. However, fetching keys from URLs specified in untrusted tokens introduces security risks and should only be done with proper validation.

Common JWT Payload Claims

The payload contains claims that provide information about the user and the token itself. These claims are categorized as registered, public, and private claims.

Claim Name Description
sub Subject User identifier
iss Issuer Who issued the token
aud Audience Intended recipient
exp Expiration Token expiry timestamp
nbf Not Before Token valid from
iat Issued At Token creation time
jti JWT ID Unique token identifier

Registered claims are standardized by the JWT specification and have specific meanings. The exp claim is particularly critical for security. If a token does not include an expiration time, or if the server fails to validate it, the token remains valid indefinitely, creating a serious vulnerability if the token is ever leaked. The iat claim records when the token was created and can be used to determine token age or to implement token rotation policies.

Beyond these registered claims, applications typically include custom claims for user roles, permissions, email addresses, and other application-specific data. For example, a token might include "role": "admin" or "permissions": ["read", "write", "delete"]. These custom claims should be inspected carefully, but never trusted without signature verification.

Decoding vs Verifying

The distinction between decoding and verifying is the single most important concept to understand when working with JWTs. Confusing the two is a common source of security vulnerabilities.

Decoding simply Base64URL-decodes the header and payload. Anyone can decode a JWT without any secret key. You can decode any JWT by pasting it into a decoder tool or running a simple Base64URL decode function on the first two segments. This makes JWTs useful for inspection and debugging, but it also means that the payload contents are publicly readable.

Verifying checks the signature using a secret key (HMAC) or public key (RSA/ECDSA). This confirms the token has not been tampered with and that it was issued by a trusted party. Verification is the step that provides security. Without it, an attacker could modify the payload to change the user ID or role, re-encode the header and payload, and create a new signature that would appear valid to a server that skips or improperly implements signature verification.

Algorithm Confusion Attacks

One of the most dangerous JWT vulnerabilities is the algorithm confusion attack, also known as the key confusion attack. This attack exploits the fact that the algorithm is specified in the header, which is attacker-controlled since the header is only Base64URL-encoded and not encrypted.

In a typical scenario, a server uses RS256 (RSA with SHA-256) for signing tokens. The server's public key is widely available, while the private key is kept secret. An attacker intercepts a valid token and changes the alg header from RS256 to HS256. Since HS256 uses a symmetric secret key, the server will try to verify the signature using the HS256 algorithm. The critical mistake is if the server uses the RS256 public key as the HS256 secret. Since the public key is public, the attacker can compute a valid HS256 signature for any payload using the public key as the HMAC secret. The server then verifies the signature using the same public key and accepts the forged token.

To prevent this attack, servers must either validate that the algorithm matches the expected algorithm before verifying, or use a library that is immune to this attack by design. Many modern JWT libraries now use separate methods for asymmetric and symmetric verification to eliminate this risk.

Safe Decoding Steps

Follow these steps every time you need to decode and inspect a JWT token.

  1. Copy the JWT token from your application. Make sure you have captured the complete token including all three segments separated by dots. A partial token will fail to decode properly.

  2. Use a trusted decoder tool like JWT Decoder that runs entirely in the browser or on your local machine. Avoid pasting tokens into unknown online tools that may log or transmit your tokens to remote servers. Even if a token is short-lived, exposing it to a third party could allow them to replay it within its validity window.

  3. Inspect the header for the algorithm. Confirm that the algorithm matches what you expect your server to use. If you see an unexpected algorithm like none or a switch from RS256 to HS256, treat the token as suspicious.

  4. Review the payload claims. Check the exp (expiration) and nbf (not before) timestamps to confirm the token is currently valid. Verify that the iss (issuer) matches your server's identifier. Check that the aud (audience) claim, if present, includes your application.

  5. Never share the token publicly. Tokens posted to bug reports, Stack Overflow questions, or GitHub gists can be abused by anyone who finds them before they expire. Always redact or replace tokens with dummy examples before sharing.

  6. Never verify with untrusted keys. If your verification code accepts keys from user input, remote URLs, or configuration files that can be modified by attackers, your entire authentication system is compromised.

Decoding JWTs in Code

While online tools are convenient for ad-hoc inspection, you will often need to decode JWTs programmatically in your application. Here is how to decode a JWT payload in a few popular programming languages without performing signature verification.

JavaScript (Node.js)

function decodeJWT(token) {
  const payload = token.split('.')[1];
  const decoded = Buffer.from(payload, 'base64url').toString('utf8');
  return JSON.parse(decoded);
}

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
console.log(decodeJWT(token));

Python

import base64
import json

def decode_jwt(token):
    payload = token.split('.')[1]
    # Add padding for base64 decoding
    padding = 4 - len(payload) % 4
    if padding != 4:
        payload += '=' * padding
    decoded = base64.urlsafe_b64decode(payload)
    return json.loads(decoded)

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(decode_jwt(token))

These examples demonstrate decoding for inspection purposes only. For production verification, always use a well-audited JWT library that handles algorithm validation, key management, and signature verification correctly.

Security Best Practices

Beyond decoding safely, following these best practices will keep your JWT implementation secure.

Use short expiration times. Access tokens should expire within 15 to 60 minutes. Short-lived tokens limit the damage if a token is leaked. Combine access tokens with refresh tokens that have longer lifetimes and can be revoked individually.

Validate all required claims. Always check exp, nbf, iss, and aud claims during verification. Many attacks exploit missing or incorrect claim validation.

Use asymmetric algorithms for distributed systems. RS256 or ES256 allow the server to sign tokens with a private key while any service can verify them with the public key. This eliminates the need to share secrets across services.

Implement token blacklisting. For scenarios where you need to revoke tokens before they expire, maintain a blacklist or use a token version number stored in your database. Increment the version on password changes or account suspension to invalidate all existing tokens.

Security Warning

Never accept tokens from untrusted sources. Always verify the signature on your server before trusting the payload. Be aware of algorithm confusion attacks where an attacker changes RS256 to HS256 to use the public key as a secret. Use reputable JWT libraries that implement signature verification securely, and keep them updated to benefit from the latest security patches.

Remember that decoding is not verifying. Anyone can read the contents of a JWT by simply Base64-decoding the payload. Treat JWT payloads as public information when it comes to confidentiality, and rely on signature verification for integrity and authenticity.


About this article

Learn how to decode JWT tokens safely to inspect their contents without compromising security.

Help2Code Logo
Menu