Skip to content

Commit d0b4c4c

Browse files
committed
refactor(backend,express): resolve relative proxyUrl in backend
When proxyUrl is a relative path (e.g. '/__clerk'), the backend now resolves it against the request's public origin derived from x-forwarded-* headers. This eliminates duplicated forwarded-header parsing in SDK packages like Express, which can now pass just the path instead of computing the full URL themselves.
1 parent 387d855 commit d0b4c4c

2 files changed

Lines changed: 17 additions & 13 deletions

File tree

packages/backend/src/tokens/authenticateContext.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ class AuthenticateContext implements AuthenticateContext {
8585

8686
Object.assign(this, options);
8787
this.clerkUrl = this.clerkRequest.clerkUrl;
88+
89+
// Resolve relative proxyUrl to absolute using the request's public origin.
90+
if (this.proxyUrl?.startsWith('/')) {
91+
this.proxyUrl = `${this.clerkUrl.origin}${this.proxyUrl}`;
92+
}
8893
}
8994

9095
public usesSuffixedCookies(): boolean {
@@ -250,6 +255,14 @@ class AuthenticateContext implements AuthenticateContext {
250255
assertValidPublishableKey(options.publishableKey);
251256
this.publishableKey = options.publishableKey;
252257

258+
// If proxyUrl is a relative path (e.g. '/__clerk'), resolve it against the
259+
// request's public origin (derived from x-forwarded-* headers by ClerkRequest).
260+
// This lets SDKs pass just the path instead of duplicating forwarded-header parsing.
261+
let resolvedProxyUrl = options.proxyUrl;
262+
if (resolvedProxyUrl?.startsWith('/')) {
263+
resolvedProxyUrl = `${this.clerkRequest.clerkUrl.origin}${resolvedProxyUrl}`;
264+
}
265+
253266
const originalPk = parsePublishableKey(this.publishableKey, {
254267
fatal: true,
255268
domain: options.domain,
@@ -259,7 +272,7 @@ class AuthenticateContext implements AuthenticateContext {
259272

260273
const pk = parsePublishableKey(this.publishableKey, {
261274
fatal: true,
262-
proxyUrl: options.proxyUrl,
275+
proxyUrl: resolvedProxyUrl,
263276
domain: options.domain,
264277
isSatellite: options.isSatellite,
265278
});

packages/express/src/authenticateRequest.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ export const authenticateAndDecorateRequest = (options: ClerkMiddlewareOptions =
164164
}
165165
}
166166

167-
// Auto-derive proxyUrl from frontendApiProxy config if not explicitly set
167+
// Pass the proxy path to authenticateRequest - the backend resolves it
168+
// against the request's public origin (from x-forwarded-* headers).
168169
let resolvedOptions = options;
169170
if (frontendApiProxy && !options.proxyUrl) {
170171
const requestUrl = new URL(request.originalUrl || request.url, `http://${request.headers.host}`);
@@ -173,17 +174,7 @@ export const authenticateAndDecorateRequest = (options: ClerkMiddlewareOptions =
173174
? frontendApiProxy.enabled(requestUrl)
174175
: frontendApiProxy.enabled;
175176
if (isProxyEnabled) {
176-
const forwardedProto = request.headers['x-forwarded-proto'];
177-
const protoHeader = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
178-
const proto = (protoHeader || '').split(',')[0].trim();
179-
const protocol = request.secure || proto === 'https' ? 'https' : 'http';
180-
181-
const forwardedHost = request.headers['x-forwarded-host'];
182-
const hostHeader = Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost;
183-
const host = (hostHeader || '').split(',')[0].trim() || request.headers.host || 'localhost';
184-
185-
const derivedProxyUrl = `${protocol}://${host}${proxyPath}`;
186-
resolvedOptions = { ...options, proxyUrl: derivedProxyUrl };
177+
resolvedOptions = { ...options, proxyUrl: proxyPath };
187178
}
188179
}
189180

0 commit comments

Comments
 (0)