Vortenza - Free Online Tools and CalculatorsBrowse tools
Published: June 17, 2026 · Updated: June 18, 202618 min readDeveloper Tools

JWT tokens explained: how to decode them safely (2026)

JWT tokens explained -- three-part token structure with header, payload, and signature

What is a JWT token?

Direct answer

A JWT (JSON Web Token) is a compact, URL-safe token format consisting of three base64url-encoded parts separated by dots: a header containing the algorithm, a payload containing claims (user data and metadata), and a signature proving the token's authenticity.

A decoded JWT is not a trusted JWT.

That distinction is the most important thing to understand about JSON Web Tokens, and it is the one most often missed by developers first working with them. Decoding a JWT is trivial. Anyone can do it, including attackers. The payload is base64-encoded, not encrypted, and base64 is reversible without any key. Decoding tells you what the token says. Verification tells you whether to believe it.

JWTs are widely used because they solve a real problem: stateless authentication at scale. When a server issues a signed token, any other server with the right key can verify it without calling back to the original server or hitting a shared session store. This scales well, works across services, and reduces infrastructure complexity. Those advantages are real.

The tradeoff is that JWT security depends entirely on correct implementation. The signature prevents tampering -- but only if you verify it. The expiration prevents indefinite use -- but only if you check it. The algorithm field specifies how the signature was made -- but only if you validate it against an allowlist rather than accepting whatever the token claims. This guide covers how JWTs actually work, how to decode and inspect them safely, and where implementations go wrong.

Key takeaways

  • --A decoded JWT is not a trusted JWT; decoding reads the payload but does not prove authenticity
  • --JWT structure has three parts: header (algorithm and type), payload (claims), and signature (cryptographic proof)
  • --The payload is base64-encoded, not encrypted; anyone with the token can read it without any key
  • --JWT security depends more on verification than on token structure -- a beautifully structured token with weak or missing verification is insecure
  • --Anyone can decode a JWT; only verification proves authenticity
  • --Short expiration times (15-60 minutes for access tokens) reduce the window for token misuse if a token is stolen
  • --The algorithm confusion attack exploits servers that accept whatever algorithm the token header claims; always verify against an explicit allowlist
  • --JWTs reduce server-side session storage but increase responsibility for token security on both client and server

JWT quick reference

ComponentPurposeSecurity role
HeaderAlgorithm metadataTells receiver how to verify the signature
PayloadClaims data (user ID, roles, expiration)Carries data -- NOT secret, readable by anyone
SignatureCryptographic proofPrevents tampering; proves authenticity
DecodeBase64url decoding (no key needed)Read contents only -- does NOT prove trust
VerifySignature check + claims validationProves the token is authentic and unmodified
Expirationexp claim -- Unix timestampLimits exposure window if token is stolen
ComponentContainsPurpose
HeaderAlgorithm type (alg), token type (typ)Tells the receiver how the signature was created
PayloadClaims: user ID, roles, expiration, issuer, etc.Carries the data the application needs
SignatureCryptographic hash of header + payloadProves the token was created by someone with the secret key and has not been modified

The three parts are base64url-encoded and joined with dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc1MDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The first . separates the header from the payload. The second . separates the payload from the signature.

What is a JWT token?

Direct answer

A JWT (JSON Web Token) is a signed, self-contained token defined by RFC 7519 that encodes claims as a JSON object. It is used primarily for authentication and authorization in web applications and APIs.

“Self-contained” is the key characteristic. Unlike a session ID (which is a random string that maps to server-stored session data), a JWT carries all the information the server needs to authenticate the request. The user's ID, their roles, when the token was issued, when it expires, and any other custom data the application needs -- all of this is in the token itself, signed so it cannot be modified without detection.

Authentication vs authorization: Authentication means proving who you are. JWTs are issued after successful authentication (login with username and password, OAuth flow, etc.). Authorization means proving what you are allowed to do. The JWT payload typically carries roles or permissions that downstream services use to determine what the bearer is allowed to access.

The stateless property is the main advantage. A traditional session requires every request to hit a session store (Redis, database) to look up the session. A JWT-based API can verify identity on any server that has the signing key, without centralized session storage. This works well for microservices, distributed systems, and third-party API access.

The tradeoff: JWT revocation is harder. You cannot “log out” a JWT the way you log out a session. Once issued, a JWT is valid until it expires, unless you implement a token blocklist (which reintroduces server-side storage). The payload of a JWT should be treated as public information unless encrypted -- do not put sensitive data in the payload.

JWT authentication flow -- user sends credentials to server, server issues signed JWT, client stores token and sends it with API requests, server verifies signature

How JWT authentication works

Direct answer

JWT authentication involves four steps: the user authenticates and receives a token, the client stores the token, the client sends the token with subsequent requests, and the server verifies the token's signature and claims on each request.

1

Step 1: User logs in

The user submits credentials (username and password, OAuth code, etc.) to the authentication endpoint. The server verifies the credentials against its database.

2

Step 2: Server creates and signs the token

If credentials are valid, the server creates a JWT payload (containing the user's ID, roles, and expiration), signs it using a secret key (for HMAC algorithms) or a private key (for RSA/ECDSA), and returns the token to the client.

3

Step 3: Client stores the token

The client stores the token -- typically in memory (for SPAs), in an HttpOnly cookie (more secure), or in localStorage (less secure, vulnerable to XSS).

4

Step 4: Client sends the token with requests

For subsequent API requests, the client includes the JWT in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
5

Step 5: Server verifies the token

On each request, the server extracts the JWT from the header, verifies the signature using the secret or public key, checks the expiration, and checks any other required claims (issuer, audience). If all checks pass, the request proceeds. The server does not store the JWT anywhere. All verification is done locally using the key and the token content.

JWT structure explained

Direct answer

A JWT has three parts -- header, payload, and signature -- each base64url-encoded and separated by dots. The header describes the signing algorithm. The payload carries the claims. The signature proves the token is authentic.

HEADER.PAYLOAD.SIGNATURE
PartFormatContainsReadable without key?
HeaderBase64url-encoded JSONAlgorithm (alg), Token type (typ)Yes -- anyone can decode this
PayloadBase64url-encoded JSONClaims (sub, exp, iat, roles, custom data)Yes -- anyone can decode this
SignatureBinary hash (base64url-encoded)HMAC or RSA/ECDSA signature of header + payloadYes, but cannot forge or verify without the key

The critical distinction: base64 encoding is not encryption. It is a reversible encoding format that converts binary data to URL-safe text. Anyone who has the JWT token can decode the header and payload into plain text in seconds. This is by design -- JWTs are meant to be inspectable.

What the signature does provide: tamper detection. If any byte in the header or payload changes after signing, the signature will not match. A server that correctly verifies the signature can trust that the claims in the payload have not been modified since the token was issued.

Real JWT example

A realistic JWT for an API service:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJhZG1pbiIsImlzcyI6ImF1dGgubXlhcHAuY29tIiwiYXVkIjoiYXBpLm15YXBwLmNvbSIsImV4cCI6MTc1MDAwMDAwMCwiaWF0IjoxNzQ5OTk2NDAwLCJqdGkiOiJhYmMxMjMifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decoded header:

{
  "alg": "HS256",
  "typ": "JWT"
}
FieldValueMeaning
algHS256HMAC-SHA256 signing algorithm
typJWTToken type (JSON Web Token)

Decoded payload:

{
  "sub": "user_123",
  "role": "admin",
  "iss": "auth.myapp.com",
  "aud": "api.myapp.com",
  "exp": 1750000000,
  "iat": 1749996400,
  "jti": "abc123"
}
FieldValueMeaning
subuser_123Subject -- the user this token represents
roleadminCustom claim -- user's role in the application
issauth.myapp.comIssuer -- the service that created the token
audapi.myapp.comAudience -- the intended recipient of the token
exp1750000000Expiration -- Unix timestamp after which the token is invalid
iat1749996400Issued at -- when the token was created
jtiabc123JWT ID -- unique identifier for this specific token

The signature:

The signature is not meaningful JSON. It is the result of:

HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret_key
)

JWT decode vs JWT verify

Direct answer

Decoding reads the JWT payload. Verifying confirms the token was created by someone with the correct key and has not been modified. Decoding alone tells you nothing about whether to trust the token.

ActionWhat it doesKey required?Security impact
Decode (base64url decode)Reads header and payload as plain JSONNoNone -- anyone can do this
Verify signatureConfirms signature matches the contentYesConfirms token was issued by expected party
Verify expirationConfirms exp claim has not passedNoPrevents use of expired tokens
Verify claimsConfirms iss, aud, and other required claimsNoPrevents token reuse across contexts
Full verificationAll of the aboveYesSecure token validation

If you decode a JWT and use the sub claim to identify the user without verifying the signature, an attacker can create their own JWT with any sub value they choose, sign it with a random key, and send it to your server. If your server only decodes and does not verify, it will accept that token and treat the request as coming from any user the attacker specifies.

The algorithm confusion attack exploits this: if a server accepts whatever algorithm the token header claims, an attacker can change "alg": "HS256" to "alg": "none" and remove the signature entirely. Always verify against an explicit algorithm allowlist, never against what the token claims.

How to decode a JWT safely

Direct answer

Decode the header and payload for inspection using base64url decoding. Verify the signature using a library with the correct key before trusting any claims in the payload. Never use decoded claims to make security decisions without verification.

Manual decoding (for inspection only):

// JavaScript -- decode only, not verify
const [headerB64, payloadB64, signature] = token.split('.');
const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));
const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
// Do NOT trust header or payload without signature verification

Proper verification (for production use):

// Node.js with jsonwebtoken library
const jwt = require('jsonwebtoken');

try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],  // Explicit allowlist, never accept 'none'
    issuer: 'auth.myapp.com',
    audience: 'api.myapp.com'
  });
  // decoded contains verified claims
  console.log(decoded.sub); // Safe to use
} catch (err) {
  // Token invalid, expired, or tampered
  return res.status(401).json({ error: 'Unauthorized' });
}
# Python with PyJWT library
import jwt

try:
    payload = jwt.decode(
        token,
        secret_key,
        algorithms=['HS256'],  # Explicit allowlist
        issuer='auth.myapp.com',
        audience='api.myapp.com'
    )
    # payload contains verified claims
except jwt.ExpiredSignatureError:
    # Token has expired
    pass
except jwt.InvalidTokenError:
    # Token is invalid for any reason
    pass
JWT decode vs verify comparison -- decode only shows readable payload with no trust, verify adds signature check and produces trusted result

Common JWT claims explained

ClaimFull nameTypeRequired?Purpose
subSubjectStringRecommendedIdentifies the principal the token is about (typically user ID)
issIssuerStringRecommendedIdentifies the entity that issued the token
audAudienceString or ArrayRecommendedIdentifies intended recipients; server should reject tokens for other audiences
expExpiration TimeUnix timestampStrongly recommendedToken must not be accepted after this time
iatIssued AtUnix timestampRecommendedWhen the token was created; use for age checks
nbfNot BeforeUnix timestampOptionalToken must not be accepted before this time
jtiJWT IDString (unique)OptionalUnique identifier for this token; enables blocklists
CustomApp-specificAny valid JSONOptionalRoles, permissions, user metadata, tenant ID, etc.

The aud claim is a security boundary. If your API server's identifier is api.myapp.com and a token has aud: mobile.myapp.com, your server should reject it even if the signature is valid. Cross-audience token use is a known attack vector.

Custom claims (like role in the example above) are not standardized. Agree on a consistent naming convention within your team and document what each custom claim means.

JWT expiration and refresh tokens

Direct answer

Access tokens should have short lifetimes (15-60 minutes). Refresh tokens have longer lifetimes (days to weeks) and are used to obtain new access tokens without re-authentication. Short access token expiration limits the damage if a token is stolen.

Access tokens

Access tokens are sent with every API request. They should be short-lived -- 15 to 60 minutes is a common range. A short lifetime means that if an access token is stolen (from a compromised device, a network log, a bug that leaks tokens), the attacker's window for using it is limited.

Refresh tokens

Refresh tokens are longer-lived (7 days, 30 days, or longer) and are sent only to the token refresh endpoint, not to every API call. When an access token expires, the client sends the refresh token to get a new access token. Refresh tokens should be stored more securely than access tokens. A common pattern: store the refresh token in an HttpOnly cookie (not accessible to JavaScript) and the access token in memory.

Token rotation

Refresh token rotation means issuing a new refresh token every time an old one is used. The old refresh token is invalidated. If a stolen refresh token is used by an attacker after the legitimate user has already used it, the server detects reuse and can flag potential compromise.

Access token:  15-60 min lifetime, sent with every request
Refresh token: 7-30 day lifetime, sent only to token endpoint
Rotation:      New refresh token on every use; old one invalidated

Common JWT security mistakes

1

Trusting decoded payloads without verification

Reading claims from the payload and making authorization decisions without calling verify(). Fix: always call your JWT library's verify() function, not its decode() function, for authenticated requests.

2

No expiration on tokens

Issuing JWTs without an exp claim means the token is valid forever. Fix: always set exp; use short lifetimes (15-60 minutes) for access tokens.

3

Weak signing secrets

Using short, guessable, or hardcoded secrets for HMAC signing. Attackers can brute-force weak secrets and forge tokens. Fix: use randomly generated secrets of at least 256 bits (32 bytes) for HS256; use RSA or ECDSA key pairs for better key management.

4

Storing tokens insecurely

Storing JWTs in localStorage makes them accessible to JavaScript, including malicious JavaScript (XSS). Fix: store access tokens in memory (for SPAs) and refresh tokens in HttpOnly cookies.

5

Leaking tokens in URLs

Including JWTs as query parameters (?token=eyJhbGc...) exposes them in browser history, server logs, and referrer headers. Fix: always send JWTs in the Authorization: Bearer header.

6

Accepting the none algorithm

Some JWT libraries historically accepted alg: none and skipped signature verification entirely. Fix: use an explicit algorithm allowlist in your library configuration.

7

Not verifying iss and aud claims

Accepting tokens from any issuer or for any audience allows token reuse across services. Fix: configure your JWT verification to require the expected issuer and audience values.

8

Never rotating or revoking tokens

No mechanism to invalidate tokens when a user logs out or a security event occurs. Fix: implement refresh token rotation and a token blocklist for critical revocation scenarios.

JWT attacks overview showing algorithm confusion, weak secret brute force, token theft, and replay attack vectors with mitigation strategies

JWT attacks developers should know

Direct answer

The main JWT attack categories are token theft (stealing a valid token), algorithm confusion (manipulating the algorithm field), weak secret attacks (brute-forcing HMAC secrets), and replay attacks (reusing a valid token beyond its intended context).

AttackHow it worksPrevention
Token theftAttacker steals a valid token via XSS, network interception, or compromised deviceShort expiration, HTTPS only, HttpOnly cookies, CSP headers
Algorithm confusion (alg: none)Token header changed to alg: none; server skips signature verificationExplicit algorithm allowlist in JWT library config
Algorithm confusion (RS256 to HS256)Server public key used as HMAC secret when library accepts algorithm switchingAlways validate algorithm against explicit allowlist
Weak secret attackAttacker brute-forces a weak HMAC secret to forge tokensUse 256+ bit randomly generated secrets; prefer RSA/ECDSA
Replay attackStolen valid token reused after the original use, before expirationShort expiration, jti claim with single-use tracking for sensitive operations
Audience confusionToken intended for service A used to access service BVerify aud claim on every token in every service
Payload tamperingAttacker modifies the payload and sends the modified tokenSignature verification catches this -- the modified payload invalidates the signature

The RS256 to HS256 attack: some JWT libraries accept whatever algorithm the token header specifies. If a server uses RS256 and a library does not enforce an allowlist, an attacker can create a token with "alg": "HS256"and sign it with the server's public key. If the library then uses the public key as the HMAC secret, the signature verifies successfully. The fix in every library is the same: specify the allowed algorithms explicitly when calling verify.

JWT decoder example

Developers routinely need to inspect JWT contents while debugging: what claims are in a token, whether it has expired, what user ID it contains, what permissions it grants. Decoding a JWT manually requires splitting on ., base64url-decoding each segment, and pretty-printing the JSON.

The Vortenza JWT Decoder decodes the header and payload into readable JSON, shows the expiration time as a human-readable date, and notes any standard claims present. Free, no signup. The important reminder: decoding without verifying is the correct and expected behavior for a debugging tool. Verification must happen server-side with the secret or public key.

For debugging a production issue, the workflow is:

1

Capture the JWT from the failing request (from logs, browser DevTools, or Postman)

2

Paste it into the decoder to inspect the payload -- check exp, sub, iss, aud

3

Identify whether the issue is an expired token, wrong audience, missing claims, or something else

4

Fix the issue in code and verify server-side with the actual key

When to use JWT vs sessions

Neither JWTs nor sessions are universally better. Pick based on your architecture and requirements.

Use JWT if...Use sessions if...
Building APIs or microservicesBuilding a traditional web app
Mobile app authenticationImmediate logout or revocation is required
Distributed systems (multiple servers)Centralized, single-server architecture
Third-party API access (no shared session store)Session-level security control is a priority
Stateless verification is preferredServer-side control over session state is needed
Cross-domain authentication (CORS with Authorization header)Cookie-based auth on a single domain
JWT vs session cookies comparison showing stateless token flow on the left and stateful server-side session storage on the right

JWT vs session cookies

FactorJWT (stateless)Session cookies (stateful)
Server-side storageNoneSession store required (Redis, database)
ScalabilityHigh -- any server with the key can verifyRequires shared session store or sticky sessions
RevocationHard -- need blocklist or short expirationEasy -- delete from session store
Token sizeLarger (full claims in token)Small (just a session ID)
Security modelClient-side token; server verifies signatureServer-side data; client holds only ID
Cross-domain useWorks well (CORS with Authorization header)Requires careful cookie domain configuration
XSS vulnerabilityHigh if stored in localStorageMitigated with HttpOnly cookies
CSRF vulnerabilityLower with Authorization headerRequires CSRF tokens
Mobile/API useNatural fitRequires cookie handling
Logout complexityHard (token still valid until exp)Easy (delete session)
Best forDistributed systems, microservices, APIsTraditional web apps, monoliths

JWT best practices

Token lifetime

  • Access tokens: 15-60 minutes maximum
  • Refresh tokens: 7-30 days, with rotation on use
  • Never issue tokens without an expiration (exp) claim

Signing and algorithms

  • Use HS256 for simple, single-service setups where the same service signs and verifies
  • Use RS256 or ES256 for multi-service architectures where issuer and verifiers are different services
  • Use randomly generated secrets of at least 256 bits for HMAC; store in environment variables, not code
  • Always specify an explicit algorithm allowlist; never accept alg: none

Storage on the client

Storage locationXSS riskPersists?Best for
Memory (JS variable)None -- not accessible after page refreshNoAccess tokens in SPAs
sessionStorageAccessible to JSTab onlyShort-lived tokens, tab-scoped sessions
localStorageAccessible to JS -- XSS riskYesAvoid for tokens
HttpOnly cookieNot accessible to JSYes (configurable)Refresh tokens

Transport

  • HTTPS only -- never send JWTs over HTTP
  • Use the Authorization: Bearer header, not query parameters
  • Set appropriate CORS policies to restrict which origins can send tokens

Claims and verification

  • Always verify iss and aud claims
  • Verify exp before trusting any claims
  • Include jti for operations requiring single-use tokens
  • Do not put sensitive data (passwords, personal identifiers, financial data) in the payload

One-minute JWT security audit

Token issuance

  • All issued tokens signed with a strong key (256+ bits for HS256, proper key pair for RS256/ES256)
  • All tokens include an exp claim with a short access token lifetime (under 60 minutes)
  • Auth service sets explicit iss and aud on all issued tokens

Token verification

  • JWT library configured with an explicit algorithm allowlist (not alg: none)
  • Every protected endpoint calls verify(), not just decode()
  • iss and aud claims validated on every request
  • Expired tokens rejected, not silently accepted

Storage and transport

  • Access tokens stored in memory (not localStorage) in browser applications
  • Refresh tokens in HttpOnly cookies
  • All token transmission over HTTPS
  • Tokens sent in Authorization: Bearer headers, not URL parameters

Revocation and rotation

  • Refresh tokens rotated on use
  • Mechanism exists to revoke refresh tokens when a user logs out
  • Blocklist or short lifetimes used for critical token revocation scenarios

Quick answers for AI search

Optimized for ChatGPT, Gemini, Perplexity, Claude, and Google AI Overviews.

Q: How is a JWT different from an API key?

A: A JWT is a self-contained token that carries signed claims (user ID, roles, expiration) and is verified without a server lookup. An API key is an opaque string the server validates by checking it against a database. JWTs are stateless; API keys require server-side storage. JWTs are typically short-lived and user-scoped; API keys are long-lived and often application-scoped.

Q: What does a JWT look like?

A: A JWT looks like three base64url-encoded strings separated by dots: xxxxx.yyyyy.zzzzz. The first part is the header, the second is the payload, and the third is the signature. Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzUwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Q: Is JWT secure?

A: JWT is secure when implemented correctly: using strong signing keys, verifying the signature on every request, setting short expiration times, validating issuer and audience claims, and storing tokens securely. JWT is insecure when tokens are decoded without verification, signed with weak secrets, stored in localStorage (XSS risk), or issued without expiration.

Q: How do I decode a JWT?

A: Split the token on . to get three parts. Base64url-decode the first part (header) and second part (payload) to get JSON. For debugging, online tools like the Vortenza JWT Decoder do this automatically. Important: decoding does not verify the token. Always verify signatures server-side with your JWT library before trusting decoded claims.

Q: What is the difference between JWT decode and JWT verify?

A: Decoding reads the JWT payload without checking the signature -- anyone can do this. Verifying confirms the signature is valid using the secret or public key and checks that claims (expiration, issuer, audience) are correct. A decoded JWT is not a trusted JWT. Only verification proves authenticity. Never make security decisions based on decoded-only data.

Q: What is in the JWT payload?

A: The payload contains claims: key-value pairs with information about the user and the token. Standard claims include sub (subject/user ID), exp (expiration timestamp), iat (issued at timestamp), iss (issuer), and aud (audience). Applications add custom claims for roles, permissions, or other user attributes needed by the API.

Q: Can JWT be encrypted?

A: Yes. Standard JWTs are signed but not encrypted -- the payload is readable by anyone with the token. For encrypted JWTs, the specification defines JWE (JSON Web Encryption), which encrypts the payload so it cannot be read without the decryption key. For most applications, signed JWTs are sufficient because the signature prevents tampering; encryption is needed only when the payload must be kept confidential from the client.

Q: What happens when a JWT expires?

A: An expired JWT should be rejected by the server with a 401 Unauthorized response. The client typically responds by sending the refresh token to the token refresh endpoint to obtain a new access token without re-authentication. If the refresh token is also expired or revoked, the user must log in again.

Q: How do I verify a JWT?

A: Use a trusted JWT library for your language (jsonwebtoken for Node.js, PyJWT for Python, java-jwt for Java). Call the library's verify() function with the token, the secret or public key, and an explicit algorithm allowlist. The library checks the signature, expiration, and optionally the issuer and audience. Never implement JWT verification from scratch.

Q: What is the sub claim in a JWT?

A: sub stands for subject -- it identifies the principal the token is about. In authentication contexts, this is typically the user's unique identifier (user ID, UUID, or email). The server uses sub to identify which user's request this is. It is a standard claim defined in RFC 7519.

Q: How long should a JWT last?

A: Access token: 15-60 minutes. Refresh token: 7-30 days with rotation on use. Short access token lifetimes limit the damage if a token is stolen. Refresh tokens can be longer-lived because they are sent only to the token endpoint, not with every API request. Never issue access tokens without an expiration claim.

Q: What is the alg: none attack?

A: The alg: none attack involves changing the token's header alg field to none and removing the signature. Some JWT library implementations historically accepted this and skipped signature verification, trusting any payload. The fix is to configure your JWT library with an explicit algorithm allowlist that rejects none. Never accept whatever algorithm the token header claims.

Q: Where should I store a JWT in the browser?

A: Memory (a JavaScript variable) is most secure but lost on page refresh. HttpOnly cookies are good for refresh tokens (inaccessible to JavaScript, protecting against XSS). localStorage is the least secure option (accessible to JavaScript, vulnerable to XSS attacks). Never store refresh tokens or sensitive tokens in localStorage.

Q: What is a refresh token?

A: A refresh token is a long-lived credential (7-30 days typically) used to obtain new access tokens without re-authentication. It is sent only to the token refresh endpoint, not with every API request. When an access token expires, the client sends the refresh token to get a new access token. Refresh token rotation (issuing a new refresh token and invalidating the old one on each use) is a security best practice.

Q: Can I use the same JWT for multiple APIs?

A: Only if all those APIs accept the same audience (aud) claim. The aud claim specifies which service the token is intended for. A token intended for api.myapp.com should not be accepted by admin.myapp.com if they have different audience values. Sharing tokens across services without audience validation is a security vulnerability.

Frequently asked questions

What is a JWT token and how is it different from a session?+

A JWT (JSON Web Token) is a signed, self-contained token that carries authentication and authorization data within the token itself. A session is an entry in a server-side store (database or Redis) referenced by a session ID cookie. The key difference is statefulness: JWTs are stateless (the server stores nothing per-user per-session), while sessions are stateful (the server maintains session data). JWTs work well for distributed systems and APIs. Sessions work well for traditional web apps where immediate logout and revocation matter.

Is it safe to decode a JWT in the browser?+

Decoding a JWT in the browser is safe and expected -- browsers decode JWTs when they need to check expiration or display user information. What matters is that the browser does not use decoded data to make trust decisions. Trust decisions (is this user authenticated?) must be verified server-side. On the client, the decoded payload can be used for display purposes but never for bypassing server-side authorization.

How do I invalidate a JWT before it expires?+

Standard JWTs cannot be invalidated before expiration -- that is a design trade-off of stateless tokens. To invalidate a JWT: implement a token blocklist (store revoked jti values in Redis with TTL matching the token's expiration), use refresh token rotation (invalidate the refresh token so new access tokens cannot be obtained), or use very short access token lifetimes so the exposure window is minimal. For high-security applications, maintaining a jti blocklist is the most reliable revocation mechanism.

What is JWT and how does it work with OAuth?+

OAuth is an authorization framework that defines flows for delegating access. JWT is a token format often used within OAuth flows. In OAuth 2.0 with OpenID Connect, the access token (used to access APIs) and the ID token (containing user identity claims) are typically issued as JWTs. The resource server (your API) verifies the JWT access token to confirm the request is authorized. The JWT format makes these tokens self-contained -- the API can verify them without calling back to the authorization server on every request.

Can JWT payloads be encrypted?+

Yes, using JWE (JSON Web Encryption) as defined in RFC 7516. A standard JWT (technically JWS -- JSON Web Signature) is signed but not encrypted. Anyone with the token can read the payload. JWE encrypts the payload so it is unreadable without the decryption key. JWE is appropriate when the payload contains sensitive data that must be kept confidential from the token bearer. For most authentication use cases, signed JWTs are sufficient because the payload should not contain sensitive data, and the signature prevents tampering.

What JWT library should I use?+

Use an actively maintained library specific to your language. For Node.js: jsonwebtoken (most widely used) or jose. For Python: PyJWT. For Java: java-jwt (Auth0) or jjwt. For Go: golang-jwt/jwt. For PHP: firebase/php-jwt. Choose a library that is actively maintained, has security advisories tracked, and explicitly supports algorithm allowlisting. Check the library's CVE history before using it in production -- several JWT libraries have had critical vulnerabilities related to algorithm confusion.

Why should I use RS256 instead of HS256?+

HS256 (HMAC-SHA256) uses a single shared secret for both signing and verification. Any service that needs to verify tokens must know the secret, which means the secret must be shared with all verifying services. If any of those services is compromised, the secret is exposed and attackers can forge tokens for any service. RS256 (RSA-SHA256) uses a key pair: the private key signs, the public key verifies. Verifying services only need the public key. If a verifying service is compromised, attackers cannot forge tokens because they do not have the private key. Use HS256 for single-service setups. Use RS256 or ES256 for multi-service architectures.

What is the aud claim and why does it matter?+

The aud (audience) claim specifies which service or services the token is intended for. If a token is issued for api.myapp.com and includes aud: api.myapp.com, a properly configured service at admin.myapp.com should reject that token even if the signature is valid. Validating audience prevents token reuse across services -- a token stolen from one part of your system cannot be used to access a different part. Configure your JWT verification to require a specific audience value and reject tokens with different or missing audience claims.

What should I put in the JWT payload?+

Include only what the consuming service needs: the user identifier (sub), roles or permissions the service needs for authorization, any tenant or organization ID for multi-tenant systems, and standard claims (iss, aud, exp, iat). Do not include: passwords, tokens or secrets, credit card or payment data, PII beyond what is necessary, or large data objects. Remember the payload is readable by anyone with the token. Keep payloads small -- every API request includes the full token in the header.

How does JWT handle user logout?+

JWTs do not have a built-in logout mechanism. When a user logs out, you can clear the access token from client storage and revoke the refresh token server-side, but any access tokens that have already been issued remain valid until they expire. To support immediate logout with JWTs: use very short access token lifetimes, maintain a server-side blocklist of revoked JTI values, or switch to a hybrid model where a per-user session version is stored server-side and included in verification.

What is the maximum size a JWT payload should be?+

There is no specification limit, but practical limits matter. JWTs are sent with every API request in the Authorization header. HTTP headers have limits (typically 8KB in common server configurations). Large payloads slow down every request. As a rule of thumb, aim for JWT payloads under 1KB. If you need to include more data, consider a reference-based approach: the JWT contains an ID, and the receiving service fetches the full data from a cache or database when needed.

Can I use JWT for WebSocket authentication?+

Yes. WebSockets do not support custom headers after the initial handshake, so the JWT must be sent during the upgrade request. Common approaches: include the JWT in the upgrade request's query parameter (?token=...), extract and verify it in the upgrade handler, then associate the verified user with the WebSocket connection. After the connection is established, subsequent messages use the established connection identity. Authenticate at connection time.

What is a JWK and how is it different from a JWT?+

A JWK (JSON Web Key) is a JSON representation of a cryptographic key, defined by RFC 7517. A JWT is a token. JWKs and JWTs are related: a JWKS (JSON Web Key Set) is a public endpoint that publishes the public keys used to verify JWTs from a particular issuer. OAuth and OIDC providers publish JWKS endpoints so that resource servers can fetch the current public keys and verify incoming JWTs without being pre-configured with specific keys.

Is JWT suitable for storing user permissions?+

Yes, with some caveats. Storing permissions in the JWT payload (like a roles array or permissions array) allows the API to authorize requests without hitting a database, which is fast and scalable. The caveats: permissions in the token are current as of when the token was issued. If permissions change (user loses a role, subscription changes), the token still reflects the old permissions until it expires. For applications where permission changes must take effect immediately, use short token lifetimes, a permissions version check, or fetch permissions from the database on each request.

What is the difference between access tokens and ID tokens in JWT?+

Access tokens authorize API access. They carry the scopes and permissions needed to access protected resources and are sent with API requests. ID tokens prove the user's identity after authentication. They carry user profile information (name, email, profile picture) and are intended for the client application, not for API servers. In OpenID Connect, the ID token is verified by the client to confirm the user's identity, while the access token is sent to resource servers. Do not send ID tokens to APIs expecting access tokens -- they serve different purposes and have different audiences.

Should JWT tokens be stored in cookies or localStorage?+

Store access tokens in memory (most secure, lost on page refresh) or in a short-lived HttpOnly cookie. Avoid localStorage -- it is accessible to JavaScript and vulnerable to XSS attacks. Refresh tokens should go in HttpOnly cookies (inaccessible to JavaScript, reducing XSS risk significantly). The rule: the more sensitive the token and the longer it lives, the more securely it should be stored. HttpOnly cookies protect against XSS but require CSRF protection. In-memory storage protects against both but requires a refresh strategy on page load.

Final verdict

JWTs work well for stateless authentication in distributed systems when implemented correctly. The three-part structure (header, payload, signature) makes them self-contained and verifiable without server-side session storage.

The security risks are real and well-documented. A decoded JWT is not a trusted JWT. Decoding is trivial and requires no key. Verification requires the correct key, an explicit algorithm allowlist, and checking that all relevant claims (expiration, issuer, audience) are valid.

The safest decoding practice: use a trusted JWT library, always call verify() not decode() for authenticated requests, configure an explicit algorithm allowlist, and validate issuer and audience claims. Use the Vortenza JWT Decoder to inspect token contents while debugging -- verification still happens server-side with the actual key.

About this guide

Published by the Vortenza Editorial Team. JWT specification referenced from RFC 7519 (JSON Web Token), RFC 7516 (JSON Web Encryption), and RFC 7517 (JSON Web Key). Algorithm confusion attack details based on security research by Tim McLean (2015) and subsequent CVEs in JWT libraries. OWASP JWT Security Cheat Sheet consulted for security recommendations. Library recommendations verified against npm, PyPI, and Maven Repository as of June 2026.

Tools used in this guide

Related guides