Articles

JWT Authentication: Best Practices & When to Use It

Explore the fundamentals of JWT authentication, from token structure and secure storage to refresh token rotation and XSS/CSRF risks. Discover scenarios where JWTs shine and when server‑side sessions are a better fit.

Written by:
APin

Senior Technology Analyst • Verified Expert

More from this author
JWT Authentication: Best Practices & When to Use It

Explore the fundamentals of JWT authentication, from token structure and secure storage to refresh token rotation and XSS/CSRF risks. Discover scenarios where JWTs shine and when server‑side sessions are a better fit.

Understanding JWTs and Their Role in Authentication

JSON Web Token (JWT) is an open standard (RFC 7519) for representing a set of claims as a compact, URL‑safe string that can be transmitted between two parties, typically a client and an authentication server. A JWT is not a JSON document; it is three Base64URL‑encoded parts concatenated with periods: header.payload.signature.

Structure of a JWT

  • Header – a JSON object that declares the token type (typ, usually "JWT") and the cryptographic algorithm used to create the signature (alg, e.g., HS256 for HMAC‑SHA‑256). The header is Base64URL‑encoded.
  • Payload (claims) – a JSON object containing statements about an entity (the user) and additional metadata. Common registered claim names include sub (subject identifier), exp (expiration time), and iat (issued‑at). Custom claims such as role or department can also be added. The payload is Base64URL‑encoded.
  • Signature – the result of applying the algorithm declared in the header to the concatenated header.payload string, using a secret key (for symmetric algorithms) or a private key (for asymmetric algorithms). The signature is then Base64URL‑encoded and appended as the third segment.

Example of a signed JWT (HS256):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJhZ2UiOjM2fQ
.4SkNQ2QZ8z5Lh7W0n2FK8KnXxXq_9yPmyMslK9YpN0A

In an authentication flow, the server validates the user's credentials, creates a JWT containing the user’s identifier and any required claims, signs it, and returns the token to the client. The client stores the token (commonly in localStorage, sessionStorage, or an HTTP‑only cookie) and includes it in the Authorization: Bearer <token> header of subsequent API requests.

How signed JWTs authenticate requests

  1. The API receives the request and extracts the token from the Authorization header.
  2. The API verifies the signature using the shared secret or public key. A valid signature guarantees that the token was issued by a trusted authority and that its claims have not been altered.
  3. After successful verification, the API checks standard claims such as exp (to ensure the token is not expired) and any application‑specific claims (e.g., role) to enforce authorization decisions.

Because the token is self‑contained, the API can make these decisions without a round‑trip to the authentication server, reducing latency and database load. However, proper storage, short expiration times, and refresh‑token rotation are essential to mitigate XSS and CSRF attack vectors, aligning the implementation with OWASP recommendations for token‑based authentication.

JWTs Within OAuth 2.0 and OpenID Connect

JSON Web Tokens (JWT) act as the standard data structure for transmitting identity and authorization claims in modern protocols. While OAuth 2.0 and OpenID Connect (OIDC) define the exchange workflows, the JWT provides a portable, self-contained, and verifiable format for the resulting tokens.

In these protocols, JWTs function as the underlying container for two distinct types of tokens, which serve different security and operational purposes:

  • ID Token: Strictly defined by OIDC as a JWT. It provides the client application with verifiable information about the user's identity (e.g., sub, email). It is intended to be consumed by the client application to personalize the UI or initialize a local session.
  • Access Token: Used within OAuth 2.0 to grant the client limited access to protected resources (APIs). While OAuth 2.0 does not mandate a specific format, the JWT has become the de facto standard for access tokens because they allow APIs to verify authorization claims locally without requiring a round-trip to the authorization server.

A critical architectural distinction is that the ID token is meant to be read by the client, whereas the access token is designed to be validated by the resource server (API). A common security error is using an ID token to authorize API requests; APIs must instead rely on the access token for authorization decisions.

To ensure security, these tokens are implemented as JSON Web Signatures (JWS). By applying a cryptographic signature—such as HMAC SHA-256—the integrity of the token's claims is guaranteed. This allows the backend to verify the token without querying a database on every request, provided the secret key remains secure. When implementing these, consider the following:

  • Signature Verification: Always validate the signature before trusting any claims contained within the payload.
  • Token Purpose: Never substitute an ID token for an access token when interacting with backend APIs.
  • Transmission: Because JWTs are base64url encoded, they are safe to transmit via HTTP Authorization headers, unlike raw JSON, which may contain URL-unsafe characters.

Secure Storage and Token Lifecycle Management

When a client receives a signed JWT, the storage mechanism determines the attack surface. localStorage and sessionStorage are accessible to any script running in the origin, making them vulnerable to cross‑site scripting (XSS). Cookies can be marked HttpOnly and Secure, which prevents JavaScript access and mitigates XSS, but they are automatically sent with every request, exposing the token to cross‑site request forgery (CSRF) unless the SameSite attribute is used. The OWASP Application Security Verification Standard (ASVS) recommends storing short‑lived access tokens in memory or an HttpOnly cookie and keeping refresh tokens out of the browser storage entirely.

  • localStorage: persists across tabs and browser restarts; convenient for single‑page apps but gives an attacker who injects script full read/write access.
  • sessionStorage: scoped to a single tab and cleared when the tab closes; reduces exposure compared to localStorage but still vulnerable to XSS.
  • Cookies: with HttpOnly; Secure; SameSite=Strict they are not readable by JavaScript and are sent only to same‑site endpoints, limiting CSRF risk.

Token expiration should be short (minutes) for access tokens so that a compromised token has limited usefulness. Refresh tokens are long‑lived but must be rotated on each use: after the client exchanges a refresh token for a new access token, the server invalidates the used refresh token and issues a fresh one. This “refresh token rotation” prevents replay attacks, a requirement highlighted in NIST SP 800‑63B and reinforced by the OWASP JWT Cheat Sheet.

A typical renewal flow looks like:


// 1. Client detects 401 or token expiry
await fetch('/auth/refresh', {
  method: 'POST',
  credentials: 'include', // sends HttpOnly cookie
  body: JSON.stringify({ refreshToken })
});
// 2. Server validates, rotates refresh token, returns new access token
// 3. Client stores new access token in memory or a secure cookie

Implementing the above mitigations—using HttpOnly cookies for refresh tokens, keeping access tokens short‑lived, rotating refresh tokens on each grant, and applying SameSite and CSRF‑token checks—aligns with SOC 2 and ISO 27001 controls for confidentiality and integrity of authentication credentials.

Mitigating XSS and CSRF Risks

Cross‑Site Scripting (XSS) occurs when an attacker injects executable code into a page that runs in the victim’s browser. The injected script can read any data that the page’s JavaScript can access, including authentication tokens stored in localStorage or sessionStorage. Because the LogRocket guide notes that JWTs are often stored in these client‑side storages, such a choice expands the XSS attack surface: a successful script can exfiltrate the token and reuse it to impersonate the user.

Cross‑Site Request Forgery (CSRF) exploits the browser’s automatic inclusion of credentials (cookies, HTTP authentication, or stored tokens) in a request triggered from a malicious site. When a JWT is kept in an HttpOnly cookie, the token is not readable by JavaScript, which mitigates XSS, but the cookie is still sent with every request to the origin. Without additional defenses, an attacker can cause the victim’s browser to issue state‑changing requests on the attacker’s behalf.

Impact of storage choices

  • localStorage / sessionStorage: easy client access, vulnerable to XSS, not sent automatically → CSRF risk is low but XSS risk is high.
  • HttpOnly SameSite cookies: invisible to JavaScript → reduces XSS exposure, but cookies are automatically attached → CSRF risk unless SameSite=Strict/Lax or anti‑CSRF tokens are used.
  • In‑memory (e.g., React state): token disappears on page reload, limiting persistence; XSS can still read it while the page is alive, but no long‑term storage reduces the window of exploitation.

Comparing XSS and CSRF implications

  • XSS can steal any client‑side token, hijack sessions, and perform actions on behalf of the user without needing a separate CSRF token.
  • CSRF relies on the browser’s credential‑auto‑submission; it cannot read tokens but can trigger requests that the server trusts if proper same‑origin checks are missing.

Recommended mitigation strategy

  • Prefer HttpOnly cookies with SameSite=Strict for JWTs to block both XSS read‑access and most CSRF vectors.
  • Implement double‑submit or synchronizer CSRF tokens for any state‑changing endpoint, as recommended by OWASP and NIST.
  • Enforce a strong Content Security Policy (CSP) that disallows inline scripts and restricts script sources.
  • Validate and sanitize all user‑generated content on the server side to prevent injection.
  • Rotate refresh tokens and set short expiration on access tokens; revoke compromised tokens promptly.
  • Document token handling in your security compliance framework (e.g., ISO 27001, SOC 2) to ensure auditability.

By aligning storage decisions with the threat model—minimizing JavaScript exposure for XSS while adding same‑site and anti‑CSRF controls for cookie‑based authentication—engineers can substantially shrink the attack surface for both XSS and CSRF.

When to Choose JWTs Over Server‑Side Sessions

Choosing between JSON Web Tokens (JWTs) and traditional server-side sessions requires a thorough evaluation of state management, scalability, and security architecture. Both mechanisms provide authentication, but they operate on fundamentally different principles.

Server-Side Sessions rely on a stateful architecture. When a user authenticates, the server generates a unique session identifier, stores it in a secure data store (e.g., Redis or a database), and sends a reference ID to the client via an HTTP cookie. Subsequent requests include this reference, which the server uses to retrieve the session state from its storage. This approach centralizes control, allowing for immediate session revocation by deleting the entry in the data store.

JWTs (JSON Web Tokens) adopt a stateless model. A JWT is a self-contained, cryptographically signed string containing claims about a user. Because the server does not need to store the session locally, it validates the token by verifying its signature using a secret key or public/private key pair. This eliminates the need for redundant database queries to look up session data for every API request.

When to Use JWTs

JWTs are ideal for scenarios requiring horizontal scalability and distributed service architectures. Their strengths include:

  • Reduced Latency: Since the server validates the token locally, it avoids the I/O overhead of querying a centralized session store.
  • Decoupled Services: In microservices architectures, different services can verify the JWT independently using a shared secret or public key without contacting a central authentication server.
  • OAuth 2.0/OIDC Integration: JWTs are the industry standard for access tokens in modern authorization frameworks, facilitating seamless identity propagation across distinct application domains.

When to Prefer Server-Side Sessions

Despite the popularity of stateless tokens, server-side sessions often provide superior security and operational simplicity in specific contexts:

  • Immediate Revocation: Because JWTs are stateless, they are difficult to invalidate before they expire. Server-side sessions allow for immediate termination of access by clearing the session from the backend.
  • Session Management: Applications that require granular control over concurrent sessions, or that need to detect and terminate anomalous behavior in real-time, benefit from the central authority of a session store.
  • Security Complexity: Implementing JWTs correctly requires handling token rotation, secure storage (to mitigate XSS risks), and complex expiration logic. Traditional sessions, managed via HttpOnly and Secure cookies, provide a well-understood security model that mitigates many client-side injection attacks.

Common Limitations and FAQs

JSON Web Tokens (JWTs) are compact, URL‑safe strings that consist of a header, payload, and signature. While they simplify stateless authentication, several intrinsic constraints affect their practical use in enterprise environments.

Key technical limitations

  • Size overhead: A signed JWT typically ranges from 300 bytes to over 1 KB depending on the number of claims and the signature algorithm. Because JWTs are sent in the Authorization: Bearer header, they contribute to the total request header size, which many servers cap at 8 KB. Excessive payloads can cause request rejection.
  • Encryption vs. signing: By default JWTs are only signed (JWS). The payload remains readable to any holder of the token. Confidential data must be placed in an encrypted JWT (JWE) or stored elsewhere; otherwise it violates confidentiality requirements such as those in NIST SP 800‑63 or ISO 27001.
  • Validation frequency: Because a JWT is self‑contained, the signature can be verified without a round‑trip to the issuer. However, revocation checks (e.g., token blacklist, rotation) require validation on every request or a short‑lived access token combined with a refresh token.

Frequently asked questions

Can I decode a JWT without the secret key?
Yes. The header and payload are Base64URL‑encoded, so they can be decoded client‑side. Decoding does not verify integrity; the signature must be validated with the secret or public key to trust the claims.
What happens when a JWT expires?
The exp claim is compared to the server’s current time. If the timestamp is in the past, the token is rejected. A typical pattern is to issue a short‑lived access token (e.g., 15 minutes) and a longer‑lived refresh token that can be exchanged for a new access token.
Should I validate JWTs on every request?
Best practice is to verify the signature, algorithm, issuer (iss), audience (aud), and expiration on each protected endpoint. This prevents replay attacks and ensures that revoked or rotated keys are respected.
Can the same JWT be used for authentication and authorization?
While technically possible, mixing concerns increases risk. An authentication token proves identity, whereas an authorization token (or separate claims) conveys permissions. Using distinct tokens or scopes lets you rotate or revoke permissions without forcing a full re‑authentication.

Practical example

// Verify a JWT in Node.js (express middleware)
const jwt = require('jsonwebtoken');
function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.sendStatus(401);
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      audience: 'my-api',
      issuer: 'auth.mycompany.com'
    });
    req.user = payload; // use claims for authz checks
    next();
  } catch (err) {
    res.sendStatus(403);
  }
}

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?

Let's Build Something Amazing Together.