Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/pink-carpets-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@clerk/clerk-sdk-node': patch
'@clerk/backend': patch
---

Simplify the signature of the low-level `authenticateRequest` helper.
- One pair of legacy or new instance keys are required instead of all 4 of them in `authenticateRequest`
- `@clerk/backend` now can handle the `"Bearer "` prefix in Authorization header for better DX
- `host` parameter is now optional in `@clerk/backend`
2 changes: 2 additions & 0 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { createBackendApiClient } from './api';
import type { CreateAuthenticateRequestOptions } from './tokens';
import { createAuthenticateRequest } from './tokens';

export type { InstanceKeys } from './tokens';
Comment thread
nikosdouvlis marked this conversation as resolved.

export * from './api/resources';
export * from './tokens';
export * from './tokens/jwt';
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/tokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export {
OptionalVerifyTokenOptions,
RequiredVerifyTokenOptions,
} from './request';
export type { InstanceKeys } from './request';
59 changes: 53 additions & 6 deletions packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,59 @@ export type OptionalVerifyTokenOptions = Partial<
>
>;

export type AuthenticateRequestOptions = RequiredVerifyTokenOptions &
type PublicKeys =
| {
publishableKey: string;
/**
* @deprecated Use `publishableKey` instead.
*/
frontendApi: never;
}
| {
publishableKey: never;
/**
* @deprecated Use `publishableKey` instead.
*/
frontendApi: string;
}
| {
publishableKey: string;
/**
* @deprecated Use `publishableKey` instead.
*/
frontendApi: string;
Comment on lines +59 to +63

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we have both?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have wayy more context that me here so please ignore if it doesn't make sense, however, the usual way to write this would be:

type ExclusiveAOrB = {
 a: string; 
 b?: never; 
} | {
 a?: never;
 b: string;
}

(notice the ?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we have both?

@nikosdouvlis Isn't this a breaking change (on the type-level) of anyone passing both?

};

type SecretKeys =
| {
secretKey: string;
/**
* @deprecated Use `secretKey` instead.
*/
apiKey: never;
}
| {
secretKey: never;
/**
* @deprecated Use `secretKey` instead.
*/
apiKey: string;
}
| {
secretKey: string;
/**
* @deprecated Use `secretKey` instead.
*/
apiKey: string;
};

export type InstanceKeys = PublicKeys & SecretKeys;

export type AuthenticateRequestOptions = InstanceKeys &
OptionalVerifyTokenOptions &
LoadResourcesOptions & {
apiVersion?: string;
apiUrl?: string;
/* Client token cookie value */
cookieToken?: string;
/* Client uat cookie value */
Expand All @@ -51,12 +101,8 @@ export type AuthenticateRequestOptions = RequiredVerifyTokenOptions &
headerToken?: string;
/* Request origin header value */
origin?: string;
/* Clerk frontend Api value */
frontendApi: string;
/* Clerk Publishable Key value */
publishableKey: string;
/* Request host header value */
host: string;
host?: string;
/* Request forwarded host value */
forwardedHost?: string;
/* Request forwarded port value */
Expand Down Expand Up @@ -105,6 +151,7 @@ export async function authenticateRequest(options: AuthenticateRequestOptions):
options.frontendApi = parsePublishableKey(options.publishableKey)?.frontendApi || options.frontendApi || '';
options.apiUrl = options.apiUrl || API_URL;
options.apiVersion = options.apiVersion || API_VERSION;
options.headerToken = options.headerToken?.replace('Bearer ', '');

assertValidSecretKey(options.secretKey || options.apiKey);

Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/util/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function checkCrossOrigin({
forwardedProto,
}: {
originURL: URL;
host: string;
host?: string | null;
forwardedHost?: string | null;
forwardedPort?: string | null;
forwardedProto?: string | null;
Expand All @@ -37,7 +37,7 @@ export function checkCrossOrigin({

const protocol = fwdProto || originProtocol;
/* The forwarded host prioritised over host to be checked against the referrer. */
const finalURL = convertHostHeaderValueToURL(forwardedHost || host, protocol);
const finalURL = convertHostHeaderValueToURL(forwardedHost || host || undefined, protocol);
finalURL.port = fwdPort || finalURL.port;

if (getPort(finalURL) !== getPort(originURL)) {
Expand All @@ -50,7 +50,7 @@ export function checkCrossOrigin({
return false;
}

export function convertHostHeaderValueToURL(host: string, protocol = 'https'): URL {
export function convertHostHeaderValueToURL(host?: string, protocol = 'https'): URL {
/**
* The protocol is added for the URL constructor to work properly.
* We do not check for the protocol at any point later on.
Expand Down
18 changes: 4 additions & 14 deletions packages/sdk-node/src/authenticateRequest.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { Clerk, RequestState } from '@clerk/backend';
import type { RequestState } from '@clerk/backend';
import { constants } from '@clerk/backend';
import cookie from 'cookie';
import type { IncomingMessage, ServerResponse } from 'http';

import { handleValueOrFn, isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl } from './shared';
import type { ClerkMiddlewareOptions } from './types';
import type { AuthenticateRequestParams, ClerkClient } from './types';
import { loadApiEnv, loadClientEnv } from './utils';

const parseCookies = (req: IncomingMessage) => {
return cookie.parse(req.headers['cookie'] || '');
};

type ClerkClient = ReturnType<typeof Clerk>;

export async function loadInterstitial({
clerkClient,
requestState,
Expand Down Expand Up @@ -40,15 +38,7 @@ export async function loadInterstitial({
return await clerkClient.remotePrivateInterstitial();
}

export const authenticateRequest = (opts: {
clerkClient: ReturnType<typeof Clerk>;
apiKey: string;
secretKey: string;
frontendApi: string;
publishableKey: string;
req: IncomingMessage;
options?: ClerkMiddlewareOptions;
}) => {
export const authenticateRequest = (opts: AuthenticateRequestParams) => {
Comment thread
nikosdouvlis marked this conversation as resolved.
const { clerkClient, apiKey, secretKey, frontendApi, publishableKey, req, options } = opts;
const { jwtKey, authorizedParties, audience } = options || {};

Expand All @@ -67,7 +57,7 @@ export const authenticateRequest = (opts: {
throw new Error(satelliteAndMissingProxyUrlAndDomain);
}

if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(secretKey)) {
if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromApiKey(secretKey || apiKey)) {
Comment thread
nikosdouvlis marked this conversation as resolved.
throw new Error(satelliteAndMissingSignInUrl);
}

Expand Down
11 changes: 10 additions & 1 deletion packages/sdk-node/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AuthenticateRequestOptions, AuthObject, SignedInAuthObject } from '@clerk/backend';
import type { AuthenticateRequestOptions, AuthObject, Clerk, InstanceKeys, SignedInAuthObject } from '@clerk/backend';
import type { MultiDomainAndOrProxy } from '@clerk/types';
import type { NextFunction, Request, Response } from 'express';
import type { IncomingMessage } from 'http';

type LegacyAuthObject<T extends AuthObject> = Pick<T, 'sessionId' | 'userId' | 'actor' | 'getToken' | 'debug'> & {
claims: AuthObject['sessionClaims'];
Expand Down Expand Up @@ -34,3 +35,11 @@ export type ClerkMiddlewareOptions = {
strict?: boolean;
signInUrl?: string;
} & MultiDomainAndOrProxy;

export type ClerkClient = ReturnType<typeof Clerk>;
Comment thread
nikosdouvlis marked this conversation as resolved.

export type AuthenticateRequestParams = InstanceKeys & {
clerkClient: ClerkClient;
req: IncomingMessage;
options?: ClerkMiddlewareOptions;
};