@clerk/express
Version:
Clerk server SDK for usage with Express
307 lines (296 loc) • 11.9 kB
JavaScript
import { a as requestHasAuthObject, i as loadClientEnv, n as incomingMessageToRequest, o as requestToProxyRequest, r as loadApiEnv, t as brandRequestAuth } from "./utils-DtX1zZA7.mjs";
import { createClerkClient } from "@clerk/backend";
import { Readable } from "stream";
import { AuthStatus, createClerkRequest, getAuthObjectForAcceptedToken } from "@clerk/backend/internal";
import { DEFAULT_PROXY_PATH, clerkFrontendApiProxy, stripTrailingSlashes } from "@clerk/backend/proxy";
import { isDevelopmentFromSecretKey } from "@clerk/shared/keys";
import { logger } from "@clerk/shared/logger";
import { isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl } from "@clerk/shared/proxy";
import { handleValueOrFn } from "@clerk/shared/utils";
import { deprecated } from "@clerk/shared/deprecated";
export * from "@clerk/backend"
//#region src/clerkClient.ts
let clerkClientSingleton = {};
const clerkClient = new Proxy(clerkClientSingleton, {
get(_target, property) {
if (property in clerkClientSingleton) return clerkClientSingleton[property];
const env = {
...loadApiEnv(),
...loadClientEnv()
};
const client = createClerkClient({
...env,
userAgent: `@clerk/express@2.1.40`
});
if (env.secretKey) clerkClientSingleton = client;
return client[property];
},
set() {
return false;
}
});
//#endregion
//#region src/errors.ts
const createErrorMessage = (msg) => {
return `🔒 Clerk: ${msg.trim()}
For more info, check out the docs: https://clerk.com/docs,
or come say hi in our discord server: https://clerk.com/discord
`;
};
const middlewareRequired = (fnName) => createErrorMessage(`The "clerkMiddleware" should be registered before using "${fnName}".
Example:
import express from 'express';
import { clerkMiddleware } from '@clerk/express';
const app = express();
app.use(clerkMiddleware());
`);
const satelliteAndMissingProxyUrlAndDomain = "Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl";
const satelliteAndMissingSignInUrl = `
Invalid signInUrl. A satellite application requires a signInUrl for development instances.
Check if signInUrl is missing from your configuration or if it is not an absolute URL.`;
//#endregion
//#region src/authenticateRequest.ts
/**
* @internal
* Authenticates an Express request by wrapping clerkClient.authenticateRequest and
* converts the express request object into a standard web request object
*
* @param opts - Configuration options for request authentication
* @param opts.clerkClient - The Clerk client instance to use for authentication
* @param opts.request - The Express request object to authenticate
* @param opts.options - Optional middleware configuration options
*/
const authenticateRequest = (opts) => {
const { clerkClient, request, options } = opts;
const { clerkClient: _clerkClient, debug: _debug, frontendApiProxy: _frontendApiProxy, isSatellite: isSatelliteInput, domain: domainInput, signInUrl: signInUrlInput, proxyUrl: proxyUrlInput, secretKey: secretKeyInput, machineSecretKey: machineSecretKeyInput, publishableKey: publishableKeyInput, ...restOptions } = options || {};
const clerkRequest = createClerkRequest(incomingMessageToRequest(request));
const env = {
...loadApiEnv(),
...loadClientEnv()
};
const secretKey = secretKeyInput || env.secretKey;
const machineSecretKey = machineSecretKeyInput || env.machineSecretKey;
const publishableKey = publishableKeyInput || env.publishableKey;
const isSatellite = handleValueOrFn(isSatelliteInput, clerkRequest.clerkUrl, env.isSatellite);
const domain = handleValueOrFn(domainInput, clerkRequest.clerkUrl) || env.domain;
const signInUrl = signInUrlInput || env.signInUrl;
const proxyUrl = absoluteProxyUrl(handleValueOrFn(proxyUrlInput, clerkRequest.clerkUrl, env.proxyUrl), clerkRequest.clerkUrl.toString());
if (isSatellite && !proxyUrl && !domain) throw new Error(satelliteAndMissingProxyUrlAndDomain);
if (isSatellite && !isHttpOrHttps(signInUrl) && isDevelopmentFromSecretKey(secretKey || "")) throw new Error(satelliteAndMissingSignInUrl);
return clerkClient.authenticateRequest(clerkRequest, {
...restOptions,
secretKey,
machineSecretKey,
publishableKey,
proxyUrl,
isSatellite,
domain,
signInUrl
});
};
const setResponseHeaders = (requestState, res) => {
if (requestState.headers) requestState.headers.forEach((value, key) => res.appendHeader(key, value));
return setResponseForHandshake(requestState, res);
};
/**
* Depending on the auth state of the request, handles applying redirects and validating that a handshake state was properly handled.
*
* Returns an error if state is handshake without a redirect, otherwise returns undefined. res.writableEnded should be checked after this method is called.
*/
const setResponseForHandshake = (requestState, res) => {
if (requestState.headers.get("location")) {
res.status(307).end();
return;
}
if (requestState.status === AuthStatus.Handshake) return /* @__PURE__ */ new Error("Clerk: unexpected handshake without redirect");
};
const absoluteProxyUrl = (relativeOrAbsoluteUrl, baseUrl) => {
if (!relativeOrAbsoluteUrl || !isValidProxyUrl(relativeOrAbsoluteUrl) || !isProxyUrlRelative(relativeOrAbsoluteUrl)) return relativeOrAbsoluteUrl;
return new URL(relativeOrAbsoluteUrl, baseUrl).toString();
};
const resolveDefaultClerkClient = (options) => {
if (!options.apiUrl && !options.apiVersion) return clerkClient;
return createClerkClient({
...loadApiEnv(),
...loadClientEnv(),
...options.apiUrl ? { apiUrl: options.apiUrl } : {},
...options.apiVersion ? { apiVersion: options.apiVersion } : {},
userAgent: `@clerk/express@2.1.40`
});
};
const authenticateAndDecorateRequest = (options = {}) => {
const clerkClient = options.clerkClient || resolveDefaultClerkClient(options);
const frontendApiProxy = options.frontendApiProxy;
const proxyPath = stripTrailingSlashes(frontendApiProxy?.path ?? DEFAULT_PROXY_PATH) || DEFAULT_PROXY_PATH;
const middleware = async (request, response, next) => {
if (requestHasAuthObject(request)) return next();
if ("auth" in request) logger.warnOnce("Clerk: another middleware has already set `req.auth` on this request. Clerk authentication will run anyway and overwrite it. To use another auth library alongside Clerk, configure it to store its state on a different request property.");
const env = {
...loadApiEnv(),
...loadClientEnv()
};
const publishableKey = options.publishableKey || env.publishableKey;
const secretKey = options.secretKey || env.secretKey;
if (frontendApiProxy) {
const requestUrl = new URL(request.originalUrl || request.url, `http://${request.headers.host}`);
if ((typeof frontendApiProxy.enabled === "function" ? frontendApiProxy.enabled(requestUrl) : frontendApiProxy.enabled) && (requestUrl.pathname === proxyPath || requestUrl.pathname.startsWith(proxyPath + "/"))) {
const proxyResponse = await clerkFrontendApiProxy(requestToProxyRequest(request), {
proxyPath,
publishableKey,
secretKey
});
response.status(proxyResponse.status);
proxyResponse.headers.forEach((value, key) => {
response.setHeader(key, value);
});
if (proxyResponse.body) {
const reader = proxyResponse.body.getReader();
new Readable({ async read() {
try {
const { done, value } = await reader.read();
if (done) this.push(null);
else this.push(Buffer.from(value));
} catch (error) {
this.destroy(error instanceof Error ? error : new Error(String(error)));
}
} }).pipe(response);
} else response.end();
return;
}
}
let resolvedOptions = options;
if (frontendApiProxy && !options.proxyUrl) {
const requestUrl = new URL(request.originalUrl || request.url, `http://${request.headers.host}`);
if (typeof frontendApiProxy.enabled === "function" ? frontendApiProxy.enabled(requestUrl) : frontendApiProxy.enabled) resolvedOptions = {
...options,
proxyUrl: proxyPath
};
}
try {
const requestState = await authenticateRequest({
clerkClient,
request,
options: resolvedOptions
});
const err = setResponseHeaders(requestState, response);
if (err) return next(err);
if (response.writableEnded) return;
const auth = brandRequestAuth((opts) => requestState.toAuth(opts));
Object.assign(request, { auth });
next();
} catch (err) {
next(err);
}
};
return middleware;
};
//#endregion
//#region src/clerkMiddleware.ts
/**
* Middleware that integrates Clerk authentication into your Express application.
* It checks the request's cookies and headers for a session JWT and, if found,
* attaches the Auth object to the request object under the `auth` key.
*
* Accepts either a static options object or a callback that receives the request
* and returns options. The callback form is useful for multi-domain setups where
* the publishable key differs per domain.
*
* @example
* app.use(clerkMiddleware(options));
*
* @example
* const clerkClient = createClerkClient({ ... });
* app.use(clerkMiddleware({ clerkClient }));
*
* @example
* app.use(clerkMiddleware());
*
* @example
* // Dynamic keys per domain
* app.use(clerkMiddleware((req) => ({
* publishableKey: req.hostname === 'example.com' ? PK_A : PK_B,
* })));
*/
const clerkMiddleware = (options = {}) => {
if (typeof options !== "function") {
const authMiddleware = authenticateAndDecorateRequest({
...options,
acceptsToken: "any"
});
return (request, response, next) => {
authMiddleware(request, response, next);
};
}
return async (request, response, next) => {
try {
authenticateAndDecorateRequest({
...await options(request),
acceptsToken: "any"
})(request, response, next);
} catch (err) {
next(err);
}
};
};
//#endregion
//#region src/getAuth.ts
/**
* Retrieves the Clerk AuthObject using the current request object.
*
* @param {GetAuthOptions} options - Optional configuration for retrieving auth object.
* @returns {AuthObject} Object with information about the request state and claims.
* @throws {Error} `clerkMiddleware` or `requireAuth` is required to be set in the middleware chain before this util is used.
*/
const getAuth = ((req, options) => {
if (!requestHasAuthObject(req)) throw new Error(middlewareRequired("getAuth"));
return getAuthObjectForAcceptedToken({
authObject: req.auth(options),
acceptsToken: options?.acceptsToken
});
});
//#endregion
//#region src/requireAuth.ts
/**
* Middleware to require authentication for user requests.
* Redirects unauthenticated requests to the sign-in url.
*
* @deprecated Use `clerkMiddleware()` with `getAuth()` instead.
* `requireAuth` will be removed in the next major version.
*
* @example
* // Before (deprecated)
* import { requireAuth } from '@clerk/express'
* router.get('/path', requireAuth(), getHandler)
*
* @example
* // After (recommended)
* import { clerkMiddleware, getAuth } from '@clerk/express'
*
* app.use(clerkMiddleware())
*
* app.get('/api/protected', (req, res) => {
* const { userId } = getAuth(req);
* if (!userId) {
* return res.status(401).json({ error: 'Unauthorized' });
* }
* // handle authenticated request
* })
*/
const requireAuth = (options = {}) => {
const authMiddleware = authenticateAndDecorateRequest({
...options,
acceptsToken: "any"
});
return (request, response, next) => {
deprecated("requireAuth", "Use `clerkMiddleware()` with `getAuth()` instead. `requireAuth` will be removed in the next major version.");
authMiddleware(request, response, (err) => {
if (err) return next(err);
const signInUrl = options.signInUrl || process.env.CLERK_SIGN_IN_URL || "/";
if (!request.auth()?.userId) return response.redirect(signInUrl);
next();
});
};
};
//#endregion
export { authenticateRequest, clerkClient, clerkMiddleware, getAuth, requireAuth };
//# sourceMappingURL=index.mjs.map