ShortIQ

ShortIQ

Development

How JWT Authentication Works: A Complete Developer Guide

JWT (JSON Web Token) is a compact, signed token used for stateless authentication in web APIs and single-page applications. This guide covers the JWT structure, signing algorithms, how the auth flow works, where to store tokens, and security best practices.

July 27, 2026ShortIQ Editorial Team

What Is a JWT?

A JSON Web Token (JWT, pronounced jot) is a compact, URL-safe token format defined in RFC 7519. JWTs are used to securely transmit information between parties as a JSON object. The information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (HMAC algorithm) or a public/private key pair (RSA or ECDSA).

In web authentication, a JWT is typically used as an access token. After a user successfully logs in, the server generates a JWT containing the user identity and permissions, signs it, and returns it to the client. The client includes the JWT in subsequent requests (usually in the Authorization header as Bearer token). The server verifies the signature on each request to authenticate the user without looking up the session in a database.

JWTs are widely used in REST APIs, single-page applications, mobile apps, and microservices architectures. They are supported by authentication providers like Auth0, Clerk, Supabase, Firebase, and AWS Cognito. Libraries for creating and validating JWTs are available in every major programming language.

The Three Parts of a JWT

A JWT consists of three Base64URL-encoded parts separated by periods: header.payload.signature. You can inspect any JWT by pasting it into a decoder — each part is independently readable (the header and payload are not encrypted by default, only signed).

The header is a JSON object containing the token type (typ: JWT) and the signing algorithm (alg: HS256, RS256, or another algorithm). The payload is a JSON object containing claims — statements about the user and the token itself. Standard claims include sub (subject, usually the user ID), iss (issuer), aud (audience), exp (expiration time), and iat (issued at). Custom claims like user roles and permissions are also commonly included.

The signature is created by combining the encoded header and payload with a secret key (for HMAC) or the private key (for RSA/ECDSA). The signature allows the server to verify that the token has not been tampered with. Because Base64URL-encoding is reversible, anyone who holds the token can read the header and payload. Sensitive data should not be stored in a JWT unless the token is also encrypted (JWE).

json
// Header (decoded)
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload (decoded)
{
  "sub": "user_01HXYZ",
  "email": "user@example.com",
  "role": "admin",
  "iat": 1719446400,
  "exp": 1719532800
}

The JWT Authentication Flow Step by Step

Step 1 — Login: The user submits their credentials (email and password) to the authentication endpoint (/api/auth/login or /api/token). The server verifies the credentials against the database. If they are correct, the server generates a JWT access token (short-lived, typically 15 minutes to 1 hour) and optionally a refresh token (long-lived, typically 7 to 30 days).

Step 2 — Token storage: The client receives the tokens and stores them. The access token is stored in memory (a JavaScript variable or React state) or in localStorage. The refresh token is stored in an HTTP-only cookie so it is not accessible to JavaScript (protecting it from XSS attacks).

Step 3 — Authenticated requests: For every subsequent API request, the client includes the access token in the Authorization header: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... The server verifies the signature, checks the expiration (exp claim), and if valid, reads the user identity from the payload claims without making a database query.

Signing Algorithms: HS256, RS256, and ES256

HS256 (HMAC-SHA256) is a symmetric algorithm. The same secret key is used to sign the token and to verify it. This means every service that needs to verify JWTs must have access to the same secret key. HS256 is simple to implement and fast, making it a good choice for systems where a single backend service both issues and verifies tokens.

RS256 (RSA-SHA256) is an asymmetric algorithm using a public/private key pair. The private key signs the token. The public key verifies the token. The public key can be shared freely — it cannot be used to forge tokens, only to verify them. RS256 is the correct choice for microservices architectures where multiple services verify tokens but only one service (the authentication server) should be able to issue them.

ES256 (ECDSA-SHA256) is also asymmetric and uses Elliptic Curve cryptography. ES256 signatures are smaller than RS256 signatures (64 bytes vs ~256 bytes) and generation is faster, but verification is comparable. ES256 is increasingly used in performance-sensitive environments and is the default in some newer authentication frameworks.

JWT vs Session-Based Authentication

Session-based authentication stores the session state on the server. When a user logs in, the server creates a session record in memory or a database and sends a session ID cookie to the client. On each request, the client sends the session ID cookie, and the server looks up the session record to identify the user. Revoking a session (on logout or account suspension) is immediate because deleting the server-side record immediately invalidates the session.

JWT-based authentication is stateless. The server does not store any session state — the token itself contains all the information needed to identify the user. This makes JWT authentication horizontally scalable without a shared session store, and it works well for APIs consumed by mobile apps and SPAs where cookie-based sessions are less natural. The trade-off is that JWTs cannot be revoked before expiration without additional infrastructure.

The inability to instantly revoke JWTs is a real operational concern. If an access token is compromised or a user needs to be logged out immediately (account suspension, password change), the server cannot invalidate the token — it remains valid until it expires. The solution is to use short expiration times (15 minutes or less) for access tokens and pair them with refresh tokens, plus maintain a token blocklist (a Redis set of revoked token IDs) for security-critical revocations.

Access Tokens vs Refresh Tokens

Access tokens are short-lived JWTs used to authenticate API requests. They expire quickly (typically 15 minutes to 1 hour) to minimize the window of exposure if a token is stolen. The short expiration means the user would need to log in again every 15 minutes — which is unacceptable for usability. Refresh tokens solve this problem.

Refresh tokens are long-lived tokens (typically 7 to 30 days) that are used only to obtain a new access token when the current one expires. The client stores the refresh token in an HTTP-only cookie and never includes it in API requests. When the access token expires, the client sends the refresh token to a dedicated endpoint (/api/auth/refresh) to receive a new access token without requiring the user to log in again.

Refresh token rotation is a security pattern where each use of a refresh token invalidates it and issues a new one. This limits the damage from a stolen refresh token: once the legitimate user uses the stolen token, their next use of the token succeeds, and the next attempt by the attacker fails. Implement refresh token rotation by storing refresh token IDs in a database and checking that the token has not already been used.

Where to Store JWTs Securely

The access token storage debate is between localStorage (or sessionStorage) and memory. localStorage persists across browser tabs and sessions but is vulnerable to XSS attacks — any JavaScript running on your page can read it, including injected scripts. Memory storage (JavaScript variables or React state) is not vulnerable to XSS but the token is lost on page refresh, requiring a silent refresh on every page load.

Refresh tokens should be stored in HTTP-only cookies with the Secure and SameSite=Strict (or SameSite=Lax) attributes. HTTP-only cookies cannot be accessed by JavaScript, which protects them from XSS. The Secure attribute ensures the cookie is only sent over HTTPS. SameSite prevents the cookie from being sent in cross-site requests, which protects against CSRF attacks.

The most secure pattern: store access tokens in memory, store refresh tokens in HTTP-only cookies, and use short access token expiration. The downside is that the access token is lost on page reload and must be re-fetched from the refresh endpoint on every page load, adding a network round-trip before the first authenticated API call.

JWT Security Best Practices

Always validate the signature before trusting any claims in a JWT. Never decode a JWT and use its payload without verifying the signature — the payload is Base64URL-encoded, not encrypted, and can be modified by anyone. Use a well-maintained JWT library that handles signature verification correctly rather than implementing it manually.

Set short expiration times on access tokens (15 minutes is common; up to 1 hour is acceptable for low-security applications). Always check the exp claim during verification. Reject tokens that are expired. Set the nbf (not before) claim for tokens that should not be used before a certain time.

Use strong, randomly generated secrets for HS256 (at minimum 256 bits of entropy). For RS256, use at least 2048-bit RSA keys. Rotate secrets periodically and after any suspected compromise. Never hardcode secrets in source code — use environment variables or a secrets manager. Restrict the aud (audience) claim to prevent tokens issued for one service from being used with another.

Common JWT Attacks and How to Prevent Them

Algorithm confusion attack (alg: none): The alg: none vulnerability occurred in early JWT libraries that accepted unsigned tokens when the alg claim was set to none. An attacker could create a token with arbitrary claims and set alg: none to bypass signature verification. Always explicitly specify which algorithms your library should accept and reject alg: none unconditionally.

HS256/RS256 confusion attack: An attacker takes an RS256 token, changes the alg header to HS256, and re-signs it using the server public key as the HS256 secret. A vulnerable server that accepts both algorithms might verify the token using the public key as an HMAC secret — which the attacker knows. Prevent this by pinning the expected algorithm in your verification configuration and never accepting both HS256 and RS256 for the same key.

Token theft via XSS: If an access token is stored in localStorage and the application has an XSS vulnerability, an attacker can steal the token and make authenticated API requests as the victim. Mitigate this by using short token expiration, storing tokens in memory rather than localStorage where possible, implementing a robust Content Security Policy, and sanitizing all user input rendered in the page.

JWT Libraries by Language

JavaScript and Node.js: jsonwebtoken is the most widely used library for signing and verifying JWTs in Node.js. jose is a modern alternative with full ES module support, TypeScript types, and support for both the Node.js crypto module and the Web Crypto API. For authentication frameworks, NextAuth.js (now Auth.js) and Passport.js both handle JWT generation and verification as part of a larger authentication system.

Python: PyJWT is the standard library for JWT handling in Python. python-jose is an alternative that also supports JSON Web Encryption (JWE). FastAPI uses python-jose in its official authentication examples. Django REST Framework supports JWT authentication through the djangorestframework-simplejwt package.

Go: golang-jwt/jwt is the maintained fork of the popular dgrijalva/jwt-go library (the original was abandoned). Lestrrat-go/jwx provides a comprehensive implementation of the full JWT, JWS, JWE, and JWK specifications. PHP: firebase/php-jwt is the standard PHP JWT library, developed by Firebase and widely used in Laravel and Symfony applications.

FAQ

What does JWT stand for?

JWT stands for JSON Web Token. It is a compact, URL-safe token format for transmitting information between parties as a digitally signed JSON object. The specification is defined in RFC 7519. JWT is pronounced jot (like the verb meaning to write briefly), not as individual letters.

Is the payload of a JWT encrypted?

No. By default, a JWT payload is Base64URL-encoded but not encrypted. Anyone who holds the token can decode and read the header and payload without any key. Only the signature requires a key to verify. Do not store sensitive data (passwords, API keys, credit card numbers) in a JWT payload unless you use JWE (JSON Web Encryption), which adds a separate encryption layer.

How do I decode a JWT?

Split the JWT at the two period characters to get the header, payload, and signature parts. Base64URL-decode each part to get the JSON. Note that Base64URL is slightly different from standard Base64 — replace - with + and _ with /, then pad with = if needed. In JavaScript: JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))) decodes the payload. Use the JWT decoder tool on this site for a visual breakdown of any JWT.

How long should a JWT access token last?

Access tokens should be short-lived: 15 minutes is a common choice for security-sensitive applications. 1 hour is acceptable for lower-risk applications. Access tokens should not last longer than the session you expect — a 24-hour access token provides little benefit over a session token while forfeiting the revocability that sessions offer. Pair short access tokens with refresh tokens to maintain user sessions without frequent logins.

Can a JWT be revoked before it expires?

Not natively. A JWT is self-contained — the server verifies it using the signature without checking a database. To revoke a JWT before expiration, you need to maintain a blocklist (a set of invalidated token IDs, typically stored in Redis) and check every incoming token against the blocklist. This adds a database lookup per request, partially defeating the stateless benefit of JWTs. Short expiration times are the primary mitigation for stolen tokens.

What is the difference between a JWT and an OAuth token?

OAuth is an authorization framework that defines flows for granting third-party access to resources. OAuth tokens can be any format — they can be opaque random strings or JWTs. When an OAuth provider uses JWTs as access tokens, the JWT carries the OAuth scopes and user identity. JWT is the token format; OAuth is the protocol for obtaining that token. Most modern OAuth providers (Auth0, Google, Microsoft) issue JWTs as their access tokens.

What should I put in the JWT payload?

Include only the data that your application needs on every request and that changes infrequently. Common payload claims: sub (user ID), email, role or roles (user permissions), iss (issuer URL), aud (audience/client ID), exp (expiration), iat (issued at). Avoid putting frequently-changing data (like a mutable profile name) in the token — it will be stale until the token is refreshed. Never put passwords, API keys, or other secrets in the payload.

What is the Authorization header format for JWTs?

The standard format is Authorization: Bearer followed by a space and then the JWT token string. For example: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzAxIiwiZXhwIjoxNzE5NTMyODAwfQ.signature. The Bearer scheme is defined in RFC 6750. Some APIs use a different prefix (like Token or JWT) but Bearer is the standard for OAuth 2.0 and most REST APIs.

Why use JWT instead of sessions?

JWTs are stateless — the server does not need to look up session data in a database or cache on every request. This makes JWTs naturally suited to microservices (multiple services can verify the same token using the public key), horizontal scaling (no shared session store needed), and mobile/SPA authentication (no reliance on cookies, which have cross-domain restrictions). Sessions are simpler to implement and easier to revoke, making them a good choice for traditional server-rendered web applications.

What is the jti claim in a JWT?

jti (JWT ID) is a unique identifier for the token, defined in RFC 7519. It is used to prevent replay attacks and to implement token revocation. If your application needs to invalidate individual tokens before expiration (for logout, password change, or account suspension), store the jti of revoked tokens in a blocklist (e.g., a Redis set) and reject any incoming token whose jti appears in the blocklist.

Related free tools

If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.

Continue Reading

Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.

Free newsletter

Get new guides in your inbox

We publish practical guides on dev tooling, prompt engineering, marketing workflows, and deployment. No fluff — straight to the point.

No spam. Unsubscribe any time.

Was this article helpful?

Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.

We use this to improve tutorials, examples, and technical depth.