VYPR
Moderate severityNVD Advisory· Published Dec 10, 2025· Updated Dec 11, 2025

Auth0 Next.js SDK has Improper Request Caching Lookup

CVE-2025-67490

Description

The Auth0 Next.js SDK is a library for implementing user authentication in Next.js applications. When using versions 4.11.0 through 4.11.2 and 4.12.0, simultaneous requests on the same client may result in improper lookups in the TokenRequestCache for the request results. This issue is fixed in versions 4.11.2 and 4.12.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@auth0/nextjs-auth0npm
>= 4.11.0, < 4.11.24.11.2
@auth0/nextjs-auth0npm
>= 4.12.0, < 4.12.14.12.1

Affected products

1

Patches

1
26cc8a7c60f4

Merge commit from fork

https://github.com/auth0/nextjs-auth0kelvinzhu-oktaNov 12, 2025via ghsa
4 files changed · +6 218
  • src/server/client.ts+1 12 modified
    @@ -52,7 +52,6 @@ import {
     } from "./session/abstract-session-store.js";
     import { StatefulSessionStore } from "./session/stateful-session-store.js";
     import { StatelessSessionStore } from "./session/stateless-session-store.js";
    -import { TokenRequestCache } from "./token-request-cache.js";
     import {
       TransactionCookieOptions,
       TransactionStore
    @@ -366,9 +365,6 @@ export class Auth0Client {
       private domain: string;
       #options: Auth0ClientOptions;
     
    -  // Cache for in-flight token requests to prevent race conditions
    -  #tokenRequestCache = new TokenRequestCache();
    -
       constructor(options: Auth0ClientOptions = {}) {
         this.#options = options;
         // Extract and validate required options
    @@ -643,14 +639,7 @@ export class Auth0Client {
           };
         }
     
    -    // Execute the token request with caching to avoid duplicate in-flight requests
    -    return this.#tokenRequestCache.execute(
    -      () => this.executeGetAccessToken(req, res, options),
    -      {
    -        options,
    -        authorizationParameters: this.#options.authorizationParameters
    -      }
    -    );
    +    return this.executeGetAccessToken(req, res, options);
       }
     
       /**
    
  • src/server/get-access-token-concurrent.test.ts+5 2 modified
    @@ -266,8 +266,11 @@ describe("Auth0Client - getAccessToken (Concurrent Calls)", () => {
         const accessToken1 = allAccessTokens.find((t) => t.audience === audience1);
         const accessToken2 = allAccessTokens.find((t) => t.audience === audience2);
     
    -    // Expect only two tokens in the session, one per audience
    -    expect(allAccessTokens).toHaveLength(2);
    +    // NOTE: Without request deduplication (cache removed), concurrent calls with
    +    // identical parameters will each execute independently. This means we may get
    +    // multiple tokens saved for the same audience/scope combination, though they
    +    // should have the same value. The test now verifies that all tokens are present.
    +    expect(allAccessTokens.length).toBeGreaterThanOrEqual(2);
     
         expect(accessToken1).toBeDefined();
         expect(accessToken1!.accessToken).toBe(token1);
    
  • src/server/token-request-cache.test.ts+0 111 removed
    @@ -1,111 +0,0 @@
    -import { describe, expect, it } from "vitest";
    -
    -import { TokenRequestCache } from "./token-request-cache.js";
    -
    -describe("Token Request Cache", () => {
    -  it("should cache when audience and scope provided", async () => {
    -    const cache = new TokenRequestCache();
    -    const options = {
    -      options: { audience: "test-audience", scope: "read:messages" },
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const requestHandler = () =>
    -      Promise.resolve({ token: "token_1", expiresAt: 1, scope: "" });
    -    const requestHandler2 = () =>
    -      Promise.resolve({ token: "token_2", expiresAt: 1, scope: "" });
    -
    -    const execute1 = cache.execute(requestHandler, options);
    -    const execute2 = cache.execute(requestHandler2, options);
    -
    -    const [res1, res2] = await Promise.all([execute1, execute2]);
    -
    -    expect(res1.token).toBe("token_1");
    -    expect(res2.token).toBe("token_1");
    -  });
    -
    -  it("should cache when only global scope and audience provided", async () => {
    -    const cache = new TokenRequestCache();
    -    const options = {
    -      options: {},
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const requestHandler = () =>
    -      Promise.resolve({ token: "token_1", expiresAt: 1, scope: "" });
    -    const requestHandler2 = () =>
    -      Promise.resolve({ token: "token_2", expiresAt: 1, scope: "" });
    -
    -    const execute1 = cache.execute(requestHandler, options);
    -    const execute2 = cache.execute(requestHandler2, options);
    -
    -    const [res1, res2] = await Promise.all([execute1, execute2]);
    -
    -    expect(res1.token).toBe("token_1");
    -    expect(res2.token).toBe("token_1");
    -  });
    -
    -  it("should not cache when same audience and different scope provided", async () => {
    -    const cache = new TokenRequestCache();
    -    const options = {
    -      options: { audience: "test-audience", scope: "read:messages" },
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const options2 = {
    -      options: { audience: "test-audience", scope: "write:messages" },
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const requestHandler = () =>
    -      Promise.resolve({ token: "token_1", expiresAt: 1, scope: "" });
    -    const requestHandler2 = () =>
    -      Promise.resolve({ token: "token_2", expiresAt: 1, scope: "" });
    -
    -    const execute1 = cache.execute(requestHandler, options);
    -    const execute2 = cache.execute(requestHandler2, options2);
    -
    -    const [res1, res2] = await Promise.all([execute1, execute2]);
    -
    -    expect(res1.token).toBe("token_1");
    -    expect(res2.token).toBe("token_2");
    -  });
    -
    -  it("should not cache when different audience and same scope provided", async () => {
    -    const cache = new TokenRequestCache();
    -    const options = {
    -      options: { audience: "test-audience", scope: "read:messages" },
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const options2 = {
    -      options: { audience: "test-audience-2", scope: "read:messages" },
    -      authorizationParameters: {
    -        audience: "test-audience",
    -        scope: "read:messages"
    -      }
    -    };
    -    const requestHandler = () =>
    -      Promise.resolve({ token: "token_1", expiresAt: 1, scope: "" });
    -    const requestHandler2 = () =>
    -      Promise.resolve({ token: "token_2", expiresAt: 1, scope: "" });
    -
    -    const execute1 = cache.execute(requestHandler, options);
    -    const execute2 = cache.execute(requestHandler2, options2);
    -
    -    const [res1, res2] = await Promise.all([execute1, execute2]);
    -
    -    expect(res1.token).toBe("token_1");
    -    expect(res2.token).toBe("token_2");
    -  });
    -});
    
  • src/server/token-request-cache.ts+0 93 removed
    @@ -1,93 +0,0 @@
    -import {
    -  AuthorizationParameters,
    -  GetAccessTokenOptions
    -} from "../types/index.js";
    -import { getScopeForAudience } from "../utils/scope-helpers.js";
    -
    -/**
    - * A generic cache to manage in-flight requests to prevent duplicate requests.
    - * This ensures that multiple simultaneous requests for the same resource
    - * (based on a generated cache key) will share the same promise and result.
    - */
    -export abstract class GenericRequestCache<TOptions, TResponse> {
    -  #cache = new Map<string, Promise<TResponse>>();
    -
    -  /**
    -   * Executes a request, caching the promise to prevent duplicate requests.
    -   * @param requestHandler Function that performs the request and returns a promise.
    -   * @param options Options used to generate the cache key.
    -   * @returns A promise that resolves to the token response.
    -   */
    -  async execute(requestHandler: () => Promise<TResponse>, options: TOptions) {
    -    // Generate a cache key based on audience and scope
    -    const cacheKey = this.getTokenCacheKey(options);
    -
    -    // See if we have an in-flight request for this key
    -    const inFlightRequest = this.#cache.get(cacheKey);
    -
    -    // If we have an in-flight request for this key,
    -    // return the existing promise to prevent duplicate requests.
    -    if (inFlightRequest) {
    -      return inFlightRequest;
    -    }
    -    // Create and cache the promise for this request
    -    const requestPromise = requestHandler().finally(() => {
    -      // Clean up the cache when the request completes (success or failure)
    -      this.#cache.delete(cacheKey);
    -    });
    -
    -    // Store the in-flight request in the cache
    -    this.#cache.set(cacheKey, requestPromise);
    -
    -    return requestPromise;
    -  }
    -
    -  /**
    -   * Generates a cache key for requests.
    -   * @param options Options used to generate the cache key.
    -   * @returns A unique cache key string
    -   */
    -  protected abstract getTokenCacheKey(options: TOptions): string;
    -}
    -
    -export type TokenRequestCacheOptions = {
    -  options: GetAccessTokenOptions;
    -  authorizationParameters?: AuthorizationParameters;
    -};
    -
    -export type TokenRequestCacheResponse = {
    -  token: string;
    -  expiresAt: number;
    -  scope?: string;
    -};
    -/**
    - * A cache to manage in-flight token requests to prevent race conditions.
    - * This ensures that multiple simultaneous requests for the same token
    - * (based on audience and scope) will share the same promise and result.
    - */
    -export class TokenRequestCache extends GenericRequestCache<
    -  TokenRequestCacheOptions,
    -  TokenRequestCacheResponse
    -> {
    -  /**
    -   * Generates a cache key for token requests based on audience and scope.
    -   * @param options The options containing audience and scope
    -   * @returns A unique cache key string
    -   */
    -  protected getTokenCacheKey({
    -    options,
    -    authorizationParameters
    -  }: {
    -    options: GetAccessTokenOptions;
    -    authorizationParameters?: AuthorizationParameters;
    -  }): string {
    -    const audience =
    -      options.audience ?? authorizationParameters?.audience ?? "";
    -    const scope =
    -      options.scope ??
    -      (authorizationParameters?.scope &&
    -        getScopeForAudience(authorizationParameters?.scope, audience)) ??
    -      "";
    -    return `${audience}:${scope}`;
    -  }
    -}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.