MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / createCachedRemoteJWKSet

Function createCachedRemoteJWKSet

apps/cloud/src/auth/jwks-cache.ts:115–183  ·  view source on GitHub ↗
(
  url: URL,
  options: CachedRemoteJWKSetOptions = {},
)

Source from the content-addressed store, hash-verified

113 * MCP auth path — see module header for why we don't just use jose's built-in.
114 */
115export const createCachedRemoteJWKSet = (
116 url: URL,
117 options: CachedRemoteJWKSetOptions = {},
118): CachedRemoteJWKSet => {
119 const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
120 const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
121 // Capture the fetch impl lazily so consumers can swap globalThis.fetch
122 // (tests do this) without us snapshotting a stale reference.
123 const fetchImpl = (): typeof globalThis.fetch =>
124 options.fetch ?? globalThis.fetch.bind(globalThis);
125
126 let entry: CacheEntry | null = null;
127 let inflight: Promise<CacheEntry> | null = null;
128
129 const refresh = (): Promise<CacheEntry> => {
130 if (inflight) return inflight;
131 inflight = (async () => {
132 const jwks = await fetchJwksOnce(url, fetchImpl(), timeoutMs);
133 const next: CacheEntry = {
134 jwks,
135 fetchedAt: Date.now(),
136 resolver: createLocalJWKSet(jwks),
137 };
138 entry = next;
139 return next;
140 })().finally(() => {
141 inflight = null;
142 });
143 return inflight;
144 };
145
146 const ensureFresh = async (forceRefresh: boolean): Promise<CacheEntry> => {
147 if (forceRefresh) return refresh();
148 if (entry && Date.now() - entry.fetchedAt < ttlMs) return entry;
149 return refresh();
150 };
151
152 const get: JWTVerifyGetKey = async (protectedHeader, token) => {
153 const current = await ensureFresh(false);
154 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: jose JWTVerifyGetKey retry path is defined by thrown resolver failures
155 try {
156 return await current.resolver(protectedHeader, token);
157 } catch (error) {
158 // Likely cause: keys rotated upstream after our TTL window started.
159 // Refetch once and try again. Anything still failing bubbles up so
160 // jose can classify it (we do not silently swallow real failures).
161 if (!isJwksNoMatchingKey(error)) {
162 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: jose JWTVerifyGetKey requires preserving upstream resolver rejection
163 throw error;
164 }
165 const refreshed = await ensureFresh(true);
166 return refreshed.resolver(protectedHeader, token);
167 }
168 };
169
170 const result = get as CachedRemoteJWKSet;
171 Object.defineProperty(result, "forceRefresh", {
172 value: () => {

Callers 4

workos.tsFile · 0.90
api-jwt-bearer.tsFile · 0.90
auth.tsFile · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected