@@ -20,8 +20,12 @@ type RequestStateContextValue = {
2020
2121export const authFnContext = createContext < ( ( options ?: PendingSessionOptions ) => AuthObject ) | null > ( null ) ;
2222export const requestStateContext = createContext < RequestStateContextValue | null > ( null ) ;
23- // Identity-free resolved options, reused by getAuth/rootAuthLoader to re-derive the request's user.
24- export const requestOptionsContext = createContext < AuthenticateRequestOptions | null > ( null ) ;
23+ // Request-INDEPENDENT config (the static clerkMiddleware options plus resolved keys)
24+ // used to re-derive auth on a Request-instance miss. It deliberately does NOT hold
25+ // resolved request-derived values (domain/proxyUrl/isSatellite); those are
26+ // re-resolved per request from args.request, so a shared context can't leak one
27+ // request's resolved options to another.
28+ export const middlewareConfigContext = createContext < ClerkMiddlewareOptions | null > ( null ) ;
2529const sharedContextProbe = createContext < Request | null > ( null ) ;
2630
2731const sharedContextMessage =
@@ -33,31 +37,37 @@ const sharedContextMessage =
3337const requestStateByRequest = new WeakMap < Request , RequestState < any > > ( ) ;
3438
3539/**
36- * Auth state for this request. Reuses what clerkMiddleware already resolved
37- * (so handshake/refresh and any machine-token verification happen once per
38- * request), and re-authenticates only when the Request instance differs from the
39- * one the middleware saw (e.g. React Router's action -> loader revalidation).
40+ * Auth state for this request. Reuses what clerkMiddleware already resolved (keyed
41+ * by Request, so handshake/refresh and any machine-token verification happen once
42+ * per request). On a Request-instance miss (e.g. React Router's action -> loader
43+ * revalidation), it re-authenticates from this request's own cookies and caches
44+ * the result so repeat calls on that Request reuse it too.
4045 */
4146export async function resolveRequestState ( args : DataFunctionArgs ) : Promise < RequestState < any > > {
4247 const cached = requestStateByRequest . get ( args . request ) ;
4348 if ( cached ) {
4449 return cached ;
4550 }
4651
47- // Miss: re-derive from this request's own cookies. `options` is identity-free
48- // config, so reading it from a possibly-shared context is safe; identity comes
49- // from args.request, so the result is always this request's user.
50- const options = IsOptIntoMiddleware ( args . context ) ? args . context . get ( requestOptionsContext ) : null ;
51- if ( ! options ) {
52+ const config = IsOptIntoMiddleware ( args . context ) ? args . context . get ( middlewareConfigContext ) : null ;
53+ if ( ! config ) {
5254 throw new Error (
5355 'Clerk: clerkMiddleware() not detected. Make sure you have installed the clerkMiddleware in your root route.' ,
5456 ) ;
5557 }
5658
57- return clerkClient ( args , options ) . authenticateRequest ( createClerkRequest ( patchRequest ( args . request ) ) , {
58- ...options ,
59- acceptsToken : 'any' ,
60- } ) ;
59+ // Re-resolve options from THIS request: domain/proxyUrl/isSatellite are derived
60+ // from args.request, and `config` carries only request-independent values (static
61+ // options + resolved keys), so this can't pick up another request's resolved
62+ // options even when the context is shared. Identity comes from args.request.
63+ const options = loadOptions ( args , config ) ;
64+ const requestState = await clerkClient ( args , config ) . authenticateRequest (
65+ createClerkRequest ( patchRequest ( args . request ) ) ,
66+ { ...options , acceptsToken : 'any' } ,
67+ ) ;
68+
69+ requestStateByRequest . set ( args . request , requestState ) ;
70+ return requestState ;
6171}
6272
6373/**
@@ -163,11 +173,21 @@ export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareFun
163173 signUpFallbackRedirectUrl : loadedOptions . signUpFallbackRedirectUrl ,
164174 } ;
165175
176+ // Request-independent config (static options + resolved keys) for the re-derive
177+ // fallback when the Request instance differs. Deliberately excludes resolved
178+ // domain/proxyUrl/isSatellite, which are re-resolved per request from args.request.
179+ const middlewareConfig : ClerkMiddlewareOptions = {
180+ ...options ,
181+ secretKey : loadedOptions . secretKey ,
182+ publishableKey : loadedOptions . publishableKey ,
183+ jwtKey,
184+ machineSecretKey,
185+ } ;
186+
166187 // Cache the resolved state keyed by this Request so getAuth/rootAuthLoader reuse
167- // it. Stash identity-free options for the re-derive fallback when the Request
168- // instance differs. authFnContext/requestStateContext remain for back-compat.
188+ // it. authFnContext/requestStateContext remain for back-compat.
169189 requestStateByRequest . set ( args . request , requestState ) ;
170- args . context . set ( requestOptionsContext , authenticateOptions ) ;
190+ args . context . set ( middlewareConfigContext , middlewareConfig ) ;
171191 args . context . set ( authFnContext , ( opts ?: PendingSessionOptions ) => requestState . toAuth ( opts ) ) ;
172192 args . context . set ( requestStateContext , { requestState, additionalState } ) ;
173193
0 commit comments