Skip to content

Commit b5eb252

Browse files
Giannis Katsanospanteliselef
authored andcommitted
feat(clerk-sdk-node): Multi-domain support
Added support for the multi-domain feature in our Node SDK. The authenticateRequest method now accepts four additional options; isSatellite, proxyUrl, domain and signInUrl.
1 parent 6fd51e8 commit b5eb252

5 files changed

Lines changed: 69 additions & 4 deletions

File tree

packages/backend/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const Headers = {
2323
EnableDebug: 'x-clerk-debug',
2424
Authorization: 'authorization',
2525
ForwardedPort: 'x-forwarded-port',
26+
ForwardedProto: 'x-forwarded-proto',
2627
ForwardedHost: 'x-forwarded-host',
2728
Referrer: 'referer',
2829
UserAgent: 'user-agent',

packages/sdk-node/src/authenticateRequest.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Clerk, RequestState } from '@clerk/backend';
22
import { constants } from '@clerk/backend';
3+
import { handleValueOrFn, isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl } from '@clerk/shared';
34
import cookie from 'cookie';
45
import type { IncomingMessage, ServerResponse } from 'http';
56

@@ -21,6 +22,25 @@ export const authenticateRequest = (opts: {
2122
const { clerkClient, apiKey, secretKey, frontendApi, publishableKey, req, options } = opts;
2223
const cookies = parseCookies(req);
2324
const { jwtKey, authorizedParties } = options || {};
25+
26+
const requestUrl = getRequestUrl(req);
27+
const isSatellite =
28+
handleValueOrFn(options?.isSatellite, requestUrl) || process.env.CLERK_IS_SATELLITE === 'true' || false;
29+
const domain = handleValueOrFn(options?.domain, requestUrl) || process.env.CLERK_DOMAIN || '';
30+
const signInUrl = options?.signInUrl || process.env.CLERK_SIGN_IN_URL || '';
31+
const proxyUrl = absoluteProxyUrl(
32+
handleValueOrFn(options?.proxyUrl, requestUrl, process.env.CLERK_PROXY_URL) as string,
33+
requestUrl.toString(),
34+
);
35+
36+
if (isSatellite && !proxyUrl && !domain) {
37+
throw new Error(satelliteAndMissingProxyUrlAndDomain);
38+
}
39+
40+
if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(secretKey)) {
41+
throw new Error(satelliteAndMissingSignInUrl);
42+
}
43+
2444
return clerkClient.authenticateRequest({
2545
apiKey,
2646
secretKey,
@@ -35,7 +55,11 @@ export const authenticateRequest = (opts: {
3555
forwardedPort: req.headers[constants.Headers.ForwardedPort] as string,
3656
forwardedHost: req.headers[constants.Headers.ForwardedHost] as string,
3757
referrer: req.headers.referer,
38-
userAgent: req.headers['user-agent'] as string,
58+
userAgent: req.headers[constants.Headers.UserAgent] as string,
59+
proxyUrl,
60+
isSatellite,
61+
domain,
62+
signInUrl,
3963
});
4064
};
4165
export const handleUnknownCase = (res: ServerResponse, requestState: RequestState) => {
@@ -57,3 +81,40 @@ export const decorateResponseWithObservabilityHeaders = (res: ServerResponse, re
5781
requestState.reason && res.setHeader(constants.Headers.AuthReason, encodeURIComponent(requestState.reason));
5882
requestState.status && res.setHeader(constants.Headers.AuthStatus, encodeURIComponent(requestState.status));
5983
};
84+
85+
const isDevelopmentFromApiKey = (apiKey: string): boolean =>
86+
apiKey.startsWith('test_') || apiKey.startsWith('sk_test_');
87+
88+
const getRequestUrl = (req: IncomingMessage): URL => {
89+
return new URL(req.url as string, `${getRequestProto(req)}://${req.headers.host}`);
90+
};
91+
92+
const getRequestProto = (req: IncomingMessage): string => {
93+
// @ts-ignore Optimistic attempt to get the protocol in case
94+
// req extends IncomingMessage in a useful way. No guarantee
95+
// it'll work.
96+
const mightWork = req.connection?.encrypted ? 'https' : 'http';
97+
// The x-forwarded-proto header takes precedence.
98+
const proto = (req.headers[constants.Headers.ForwardedProto] as string) || mightWork;
99+
if (!proto) {
100+
throw new Error(missingProto);
101+
}
102+
// Sometimes the x-forwarded-proto header does not come as a
103+
// single value.
104+
return proto.split(',')[0].trim();
105+
};
106+
107+
const absoluteProxyUrl = (relativeOrAbsoluteUrl: string, baseUrl: string): string => {
108+
if (!relativeOrAbsoluteUrl || !isValidProxyUrl(relativeOrAbsoluteUrl) || !isProxyUrlRelative(relativeOrAbsoluteUrl)) {
109+
return relativeOrAbsoluteUrl;
110+
}
111+
return new URL(relativeOrAbsoluteUrl, baseUrl).toString();
112+
};
113+
114+
const satelliteAndMissingProxyUrlAndDomain =
115+
'Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl';
116+
const satelliteAndMissingSignInUrl = `
117+
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
118+
Check if signInUrl is missing from your configuration or it is not a absolute URL.`;
119+
const missingProto =
120+
"Cannot determine the request protocol. Please make sure you've set the X-Forwarded-Proto header with the request protocol (http or https).";

packages/sdk-node/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AuthObject, SignedInAuthObject } from '@clerk/backend';
2+
import type { MultiDomainAndOrProxy } from '@clerk/types';
23
import type { NextFunction, Request, Response } from 'express';
34

45
type LegacyAuthObject<T extends AuthObject> = Pick<T, 'sessionId' | 'userId' | 'actor' | 'getToken' | 'debug'> & {
@@ -30,4 +31,5 @@ export type ClerkMiddlewareOptions = {
3031
authorizedParties?: string[];
3132
jwtKey?: string;
3233
strict?: boolean;
33-
};
34+
signInUrl?: string;
35+
} & MultiDomainAndOrProxy;

playground/express/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ npm run build && npm run yalc:all
1010
Execute in current folder:
1111

1212
```bash
13-
touch .env # set PUBLISHABLE_KEY and SECRET_KEY from Clerk Dashboard API keys
13+
touch .env # set PUBLISHABLE_KEY, SECRET_KEY and JWT_KEY from Clerk Dashboard API keys
1414
npm i
1515
rm -rf node_modules/@clerk
1616
yalc add @clerk/clerk-sdk-node # also add the packages you made changes to, e.g. @clerk/backend @clerk/types.

playground/express/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"start": "ts-node ./src/server.ts"
6+
"start": "ts-node ./src/server.ts",
7+
"yalc:add": "yalc add -- @clerk/types @clerk/backend @clerk/clerk-sdk-node"
78
},
89
"author": "",
910
"license": "ISC",

0 commit comments

Comments
 (0)