What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token format used to transmit claims between parties. It consists of three parts: a header, a payload, and a signature — each base64url-encoded and separated by dots. The signature ensures the token hasn't been tampered with.
JWTs are the backbone of stateless authentication in modern APIs. Services like Auth0, Firebase, and custom backends all rely on them. You can inspect the contents of any JWT instantly using a JWT Decoder — paste the token and see the decoded header and payload in a readable format, which is invaluable during development and debugging.
The Three Parts of a JWT
Header
The header typically contains the token type (JWT) and the signing algorithm (HS256, RS256, etc.). The algorithm in the header tells the verification library which cryptographic method to use, which is why the signature must be verified against the header's declared algorithm — never trust a token that instructs the verifier to use "none" as the algorithm, as this was the basis for several high-profile JWT library vulnerabilities.
Payload
The payload contains claims — statements about the user and additional metadata like expiration time (exp), issuer (iss), and subject (sub). Never put sensitive data here as the payload is readable by anyone. Common registered claims include iat (issued at), nbf (not before), and jti (JWT ID for preventing replay attacks). Custom claims should be namespaced to avoid collisions with registered claim names.
Signature
The signature prevents tampering. It's created by combining the encoded header, encoded payload, and a secret key using the specified algorithm. The receiving party recalculates the signature using the same secret — if it matches, the token is verified. If it does not match, the token has been modified and must be rejected immediately.
JWT Verification Flow
When a server receives a JWT from a client, the verification process follows a strict sequence. First, the server decodes the token to extract the header and payload — a JWT Decoder does this instantly for debugging purposes. Next, it checks that the algorithm in the header matches the expected algorithm (rejecting "none" algorithm attacks by default). Then it verifies the signature using the secret or public key. After signature verification, it validates the standard claims: exp must be in the future, nbf must be in the past, iss must match the expected issuer, and aud must include the receiving server's identifier. Finally, if the token includes a jti claim, the server checks that this ID has not been used before to prevent replay attacks. Every step in this chain must pass or the token is rejected.
Access Tokens vs. Refresh Tokens
In a typical JWT-based authentication system, the server issues two tokens upon login. The access token is short-lived (15-60 minutes) and is sent with each API request in the Authorization header using the Bearer scheme. The refresh token is long-lived (days or weeks) and is used exclusively to obtain new access tokens without requiring the user to log in again. This two-token strategy balances security with usability — if an access token is stolen, its short lifespan limits the damage window. Refresh tokens should be stored more securely (httpOnly cookies are recommended over localStorage) and can be revoked server-side. When building JWT auth, always implement refresh token rotation: each time a refresh token is used, issue a new one and invalidate the old. This limits the window during which a stolen refresh token is usable. Have our JWT Decoder ready to inspect both token types during development to confirm the correct claims and expiration times are being set.
Choosing Between HS256 and RS256
JWT supports multiple signing algorithms, but two dominate in practice. HS256 (HMAC with SHA-256) is a symmetric algorithm — the same secret is used to sign and verify the token. It is fast and simple but requires the secret to be shared between the issuer and verifier, making it unsuitable for third-party verification or microservice architectures where multiple services need independent verification. RS256 (RSA with SHA-256) is asymmetric — a private key signs the token and a public key verifies it. The public key can be distributed freely, making RS256 the standard for OpenID Connect providers like Google, Auth0, and Azure AD. In microservice architectures, RS256 allows the auth service to sign tokens while all other services verify them using the public key without ever accessing the private signing key. For most production systems, RS256 is the safer choice because the private key never leaves the issuing service.
Common JWT Pitfalls
- Storing secrets in the payload: The payload is base64-encoded, not encrypted. Anyone who intercepts the token can decode it using a JWT Decoder — never put passwords, API keys, or personal data in the payload.
- Not checking expiration: Always validate the exp claim server-side to prevent replay attacks with expired tokens. A token that never expires is a permanent security risk if stolen.
- Weak signing keys: Use a cryptographically strong random key of at least 256 bits for HS256 or a 2048-bit RSA key pair for RS256. Never use dictionary words, short strings, or reused passwords as your signing secret.
- Algorithm confusion attacks: Always specify the expected algorithm server-side and reject tokens that change it. An attacker could change RS256 to HS256 and then sign tokens using the public key if the server blindly trusts the header's alg field without verification.