-
Notifications
You must be signed in to change notification settings - Fork 439
Description
Description
When two different authenticated users concurrently call getAccessToken() with the same (audience, scope), the second call may reuse the first call's in-flight promise and receive the first user's access token. This leaks tokens across sessions and can break authz/isolation guarantees. With DPoP, misuse is limited but the token value still leaks.
The problem stems from the fact that TokenRequestCache computes the cache key from audience + scope only, ignoring the caller's session/user (and DPoP key). The cache is shared per Auth0Client instance; the official guidance encourages a process-wide singleton, so concurrent requests from different users hit the same in-memory cache.
Conditions to exploit:
- A singleton Auth0Client instance
- Two+ users request a token with the same (audience, scope) at the same time on the same runtime/worker.
Reproduction
Log in as User A and User B (separate browsers or sessions).
From both, fire parallel requests to a route that calls auth0.getAccessToken(req, res, { audience: X, scope: '....', refresh: true }).
Observe cases where both responses return identical token, and only User A's session gets persisted changes (if any).
Additional context
Here's an unfinished patch which gives a rough idea of the issue + a path towards a true fix:
diff --git a/src/server/token-request-cache.ts b/src/server/token-request-cache.ts
index dab3a387..ee960abd 100644
--- a/src/server/token-request-cache.ts
+++ b/src/server/token-request-cache.ts
@@ -53,6 +53,7 @@ export abstract class GenericRequestCache<TOptions, TResponse> {
export type TokenRequestCacheOptions = {
options: GetAccessTokenOptions;
authorizationParameters?: AuthorizationParameters;
+ sessionKey?: string;
};
export type TokenRequestCacheResponse = {
@@ -76,10 +77,12 @@ export class TokenRequestCache extends GenericRequestCache<
*/
protected getTokenCacheKey({
options,
- authorizationParameters
+ authorizationParameters,
+ sessionKey
}: {
options: GetAccessTokenOptions;
authorizationParameters?: AuthorizationParameters;
+ sessionKey?: string;
}): string {
const audience =
options.audience ?? authorizationParameters?.audience ?? "";
@@ -88,6 +91,7 @@ export class TokenRequestCache extends GenericRequestCache<
(authorizationParameters?.scope &&
getScopeForAudience(authorizationParameters?.scope, audience)) ??
"";
- return `${audience}:${scope}`;
+ const session = sessionKey ?? "anon";
+ return `${session}:${audience}:${scope}`;
}
}
This bug was found with ZeroPath.