ST
StringTools
Back to Blog
SecurityApril 5, 2026·11 min read·StringTools Team

JWT Tokens Explained — How to Read and Debug JSON Web Tokens

What is a JWT Token

A JSON Web Token, commonly abbreviated as JWT and pronounced like the English word jot, is a compact, URL-safe token format used to represent claims securely between two parties. JWTs are defined in RFC 7519 and have become the dominant mechanism for authentication and authorization in modern web applications and APIs. Unlike opaque session tokens that require a server-side lookup to determine their meaning, JWTs are self-contained: they carry all the necessary information within the token itself, encoded as a JSON object.

The self-contained nature of JWTs means that a server can verify the token's authenticity and extract user information without querying a database or session store. This stateless property makes JWTs particularly well-suited for distributed systems and microservice architectures where maintaining a centralized session store is impractical or undesirable. Each service can independently verify a JWT using a shared secret or public key, eliminating a single point of failure and reducing latency by avoiding inter-service session lookups.

JWTs are used beyond just authentication. They serve as authorization tokens that carry permissions and roles, as information exchange tokens between services that need to pass verified claims, and as single sign-on tokens that allow users to authenticate once and access multiple applications. OAuth 2.0 and OpenID Connect, the protocols that power social login and enterprise identity management, rely heavily on JWTs for access tokens and ID tokens. Understanding JWTs is therefore essential for any developer building or consuming modern authentication systems.

A JWT appears as a long string of characters divided into three sections separated by dots. Despite its seemingly random appearance, a JWT is not encrypted by default. It is merely encoded in Base64URL format, which means anyone who receives the token can decode it and read its contents. The token's security comes not from hiding its contents but from the cryptographic signature that proves the token was issued by a trusted authority and has not been modified since issuance.

JWT Structure: Header, Payload, and Signature

Every JWT consists of three parts separated by dots: the header, the payload, and the signature. Each part is Base64URL-encoded independently, and the three encoded strings are concatenated with dots to form the complete token. Understanding this three-part structure is essential for reading, debugging, and working with JWTs effectively because each part serves a distinct purpose in the token's lifecycle.

The header is a JSON object that describes the token's type and the signing algorithm used to create the signature. A typical header contains two fields: typ, which is set to JWT, and alg, which specifies the algorithm such as HS256 for HMAC-SHA256 or RS256 for RSA-SHA256. The header tells the token verifier which algorithm to use when checking the signature. Some headers include additional fields like kid, a key identifier that helps the verifier select the correct key from a set when multiple signing keys are in use.

The payload is the core of the JWT and contains the claims, which are statements about the user and additional metadata. Claims come in three categories: registered claims are predefined names like iss for issuer, exp for expiration time, sub for subject, and aud for audience that provide standard, interoperable metadata. Public claims are defined by the application and should use collision-resistant names, often URIs. Private claims are custom fields agreed upon between the token issuer and consumer, such as user roles, permissions, or profile information. The payload can contain any number of claims, though keeping it small is important because the token travels with every request.

The signature is created by taking the Base64URL-encoded header, concatenating a dot, appending the Base64URL-encoded payload, and then applying the specified algorithm with a secret key or private key. For symmetric algorithms like HS256, the same secret key is used to both create and verify the signature. For asymmetric algorithms like RS256, the issuer signs with a private key and verifiers check the signature using the corresponding public key. The signature ensures that the token has not been tampered with: changing a single character in the header or payload invalidates the signature, and without the signing key, an attacker cannot produce a valid replacement signature.

How JWT Authentication Works

The JWT authentication flow begins when a user submits their credentials, typically a username and password, to an authentication endpoint. The server verifies the credentials against its user store, and if valid, creates a JWT containing claims about the user such as their user ID, email, roles, and the token's expiration time. The server signs the JWT with its secret key and returns the token to the client. The client stores this token, usually in memory for single-page applications or in a secure HTTP-only cookie, and includes it in subsequent requests to authenticate itself.

For each subsequent API request, the client sends the JWT in the Authorization header using the Bearer scheme. The server receives the request, extracts the token from the header, and performs three verification steps. First, it decodes the header and payload to read the claims. Second, it recomputes the signature using the same algorithm and key and compares it to the signature in the token, verifying that the token has not been tampered with. Third, it checks that the token has not expired by comparing the exp claim to the current time. Only if all three checks pass does the server process the request using the user information from the token's claims.

Token expiration is a critical component of JWT security. The exp claim specifies the exact time after which the token should no longer be accepted. Short-lived access tokens, typically lasting 15 minutes to an hour, limit the window of opportunity for an attacker who obtains a stolen token. When the access token expires, the client uses a longer-lived refresh token to obtain a new access token without requiring the user to re-enter credentials. This two-token pattern balances security with user convenience by keeping the frequently-transmitted access token short-lived while avoiding constant re-authentication.

In microservice architectures, JWT authentication scales elegantly because each service can verify tokens independently. The authentication service issues JWTs signed with a private key and publishes the corresponding public key, often through a JWKS endpoint. Each downstream service fetches this public key and uses it to verify incoming tokens without communicating with the authentication service for every request. This decentralized verification model reduces latency, eliminates a single point of failure, and allows services to be deployed and scaled independently.

Common JWT Claims

The JWT specification defines a set of registered claims that provide standard, interoperable metadata about the token. The iss claim identifies the entity that issued the token, such as your authentication server's domain. The sub claim identifies the subject of the token, typically the user's unique identifier. The aud claim specifies the intended recipient of the token, which allows a verifier to reject tokens that were not intended for its service. These three claims together establish who issued the token, who it represents, and who should accept it.

Time-based claims control the token's validity window. The exp claim, which stands for expiration time, is a Unix timestamp specifying when the token becomes invalid. The iat claim records the time the token was issued, and the nbf claim, meaning not before, specifies the earliest time at which the token should be accepted. Together, these claims define a precise window during which the token is valid. Verifiers should always check the exp claim and should account for reasonable clock skew, typically a few seconds, between the issuer and verifier servers.

The jti claim, short for JWT ID, is a unique identifier for the token that can be used to prevent token replay attacks. By recording the jti of each token it processes, a server can detect and reject tokens that have already been used. This is particularly important for tokens that authorize one-time actions, such as password reset links or email verification tokens, where accepting the same token twice could have undesirable consequences.

Beyond the registered claims, applications define custom claims to carry business-specific information. Common custom claims include the user's roles or permissions, the user's display name or email address, the organization or tenant ID in multi-tenant systems, and feature flags or subscription tiers. When adding custom claims, keep the payload minimal because the token is transmitted with every request. Large tokens increase bandwidth usage, slow down request processing, and may exceed size limits in HTTP headers or cookies. Store only the information that the token verifier needs to make authorization decisions, and fetch additional details from a database or user service when needed.

How to Decode and Debug JWTs

Decoding a JWT is simple because the header and payload are merely Base64URL-encoded, not encrypted. You can decode any JWT by splitting it at the dots, Base64URL-decoding each of the first two parts, and parsing the resulting JSON strings. This process reveals the algorithm in the header and all claims in the payload, including the user ID, expiration time, roles, and any other information the issuer included. The ability to read a JWT's contents is a feature, not a bug: it allows clients and debugging tools to inspect token claims without needing the signing key.

Online JWT debugging tools like jwt.io provide a convenient interface for pasting a token and seeing its decoded header, payload, and signature verification status. These tools color-code the three parts of the token, display the decoded JSON with formatting, and optionally verify the signature if you provide the signing key or public key. During development, these tools are invaluable for verifying that your authentication server is issuing tokens with the correct claims, appropriate expiration times, and the expected signing algorithm.

Command-line decoding is useful for quick inspection during development or troubleshooting. Most programming languages can decode a JWT in a few lines of code by splitting the string, applying Base64URL decoding, and parsing the JSON. For shell environments, piping the token through base64 decoding and JSON formatting utilities provides instant readability. This is particularly useful when inspecting tokens in server logs, HTTP headers, or cookie values where the raw encoded string is not human-readable.

When debugging JWT-related issues, the most common problems are expired tokens, incorrect audience claims, clock skew between services, and algorithm mismatches. Start by decoding the token to inspect the exp and iat claims, verifying that the times make sense and the token has not expired. Check that the iss and aud claims match what your verifier expects. Verify that the alg in the header matches the algorithm your verification code is configured to accept. If the signature verification fails despite correct claims, ensure that the signing key or public key is correctly configured and that there is no whitespace or encoding issue with the key material.

JWT Security Best Practices

Always use strong, asymmetric signing algorithms like RS256 or ES256 for production systems. While symmetric algorithms like HS256 are simpler to set up, they require sharing the signing secret with every service that needs to verify tokens, increasing the risk of key compromise. Asymmetric algorithms use a private key known only to the issuer and a public key that can be safely distributed to any number of verifiers. This separation of concerns ensures that even if a verifier is compromised, the attacker cannot forge new tokens.

Set short expiration times on access tokens to limit the impact of token theft. An access token with a 15-minute lifetime means that a stolen token can only be exploited for 15 minutes before it becomes useless. Pair short-lived access tokens with refresh tokens that have longer lifetimes but are stored securely and can be revoked. Implement refresh token rotation, where each use of a refresh token issues a new refresh token and invalidates the old one, to detect and mitigate token theft.

Never store sensitive information in JWT payloads. Because JWTs are merely encoded and not encrypted, anyone who intercepts a token can read its contents. Do not include passwords, credit card numbers, personal identification numbers, or other sensitive data in claims. If you need to transmit sensitive data within a token, use JSON Web Encryption to encrypt the payload, but consider whether the sensitive data truly needs to travel with every request or whether a server-side lookup would be more appropriate.

Validate all claims rigorously during token verification. Always check the signature, expiration time, issuer, and audience. Reject tokens with the alg field set to none, which is a well-known attack vector where the attacker removes the signature requirement. Use a whitelist of accepted algorithms rather than trusting the algorithm specified in the token's header. Validate that the token was issued in the past by checking the iat claim, and that it is not being used before its designated start time by checking the nbf claim.

Common JWT Mistakes to Avoid

The most dangerous JWT mistake is storing tokens in localStorage or sessionStorage where they are accessible to JavaScript. Any cross-site scripting vulnerability on your site gives an attacker access to the token, which they can then use to impersonate the user. Instead, store access tokens in memory for single-page applications and use HTTP-only, secure, same-site cookies for refresh tokens. This approach prevents JavaScript from accessing the refresh token while keeping the access token available for API requests.

Using JWTs as session replacements without implementing revocation is another common mistake. Traditional sessions can be invalidated instantly by deleting the session record from the server, but a JWT remains valid until it expires because the server does not maintain any state about issued tokens. If a user changes their password, is banned, or their account is compromised, you cannot invalidate outstanding JWTs without additional infrastructure. Implement a token blacklist, use short expiration times with refresh tokens, or maintain a version counter on the user record that is checked during verification.

Failing to validate the token audience allows tokens intended for one service to be used at another. In a system with multiple services, a JWT issued for the user profile service should not be accepted by the payment service. Without audience validation, a token stolen from a less critical service could be used to access a more sensitive one. Always set the aud claim when issuing tokens and reject tokens whose audience does not match the receiving service.

Overloading the JWT payload with excessive claims inflates the token size and creates performance and compatibility issues. Every additional claim increases the size of the token, which is transmitted in an HTTP header with every request. Some proxies and servers impose limits on header sizes, typically around 8 kilobytes, and oversized tokens will be silently truncated or rejected. Keep the payload focused on the minimum claims needed for authentication and authorization. If a service needs detailed user profile information, it should fetch it from a dedicated endpoint using the user ID from the token rather than expecting the token to carry all user data.