@clerk/nextjs
Version:
Clerk SDK for NextJS
315 lines • 13.2 kB
JavaScript
import "../chunk-BUSYA2B4.js";
import {
AuthStatus,
constants,
createClerkRequest,
createRedirect,
getAuthObjectForAcceptedToken,
isMachineTokenByPrefix,
isTokenTypeAccepted,
TokenType
} from "@clerk/backend/internal";
import { parsePublishableKey } from "@clerk/shared/keys";
import { notFound as nextjsNotFound } from "next/navigation";
import { NextResponse } from "next/server";
import { isRedirect, serverRedirectWithAuth, setHeader } from "../utils";
import { withLogger } from "../utils/debugLogger";
import { canUseKeyless } from "../utils/feature-flags";
import { clerkClient } from "./clerkClient";
import { PUBLISHABLE_KEY, SECRET_KEY, SIGN_IN_URL, SIGN_UP_URL } from "./constants";
import { createContentSecurityPolicyHeaders } from "./content-security-policy";
import { errorThrower } from "./errorThrower";
import { getHeader } from "./headers-utils";
import { getKeylessCookieValue } from "./keyless";
import { clerkMiddlewareRequestDataStorage, clerkMiddlewareRequestDataStore } from "./middleware-storage";
import {
isNextjsNotFoundError,
isNextjsRedirectError,
isNextjsUnauthorizedError,
isRedirectToSignInError,
isRedirectToSignUpError,
nextjsRedirectError,
redirectToSignInError,
redirectToSignUpError,
unauthorized
} from "./nextErrors";
import { createProtect } from "./protect";
import {
assertKey,
decorateRequest,
handleMultiDomainAndProxy,
redirectAdapter,
setRequestHeadersOnNextResponse
} from "./utils";
const clerkMiddleware = (...args) => {
const [request, event] = parseRequestAndEvent(args);
const [handler, params] = parseHandlerAndOptions(args);
const middleware = clerkMiddlewareRequestDataStorage.run(clerkMiddlewareRequestDataStore, () => {
const baseNextMiddleware = withLogger("clerkMiddleware", (logger) => async (request2, event2) => {
var _a, _b;
const resolvedParams = typeof params === "function" ? await params(request2) : params;
const keyless = await getKeylessCookieValue((name) => {
var _a2;
return (_a2 = request2.cookies.get(name)) == null ? void 0 : _a2.value;
});
const publishableKey = assertKey(
resolvedParams.publishableKey || PUBLISHABLE_KEY || (keyless == null ? void 0 : keyless.publishableKey),
() => errorThrower.throwMissingPublishableKeyError()
);
const secretKey = assertKey(
resolvedParams.secretKey || SECRET_KEY || (keyless == null ? void 0 : keyless.secretKey),
() => errorThrower.throwMissingSecretKeyError()
);
const signInUrl = resolvedParams.signInUrl || SIGN_IN_URL;
const signUpUrl = resolvedParams.signUpUrl || SIGN_UP_URL;
const options = {
publishableKey,
secretKey,
signInUrl,
signUpUrl,
...resolvedParams
};
clerkMiddlewareRequestDataStore.set("requestData", options);
const resolvedClerkClient = await clerkClient();
if (options.debug) {
logger.enable();
}
const clerkRequest = createClerkRequest(request2);
logger.debug("options", options);
logger.debug("url", () => clerkRequest.toJSON());
const authHeader = request2.headers.get(constants.Headers.Authorization);
if (authHeader && authHeader.startsWith("Basic ")) {
logger.debug("Basic Auth detected");
}
const cspHeader = request2.headers.get(constants.Headers.ContentSecurityPolicy);
if (cspHeader) {
logger.debug("Content-Security-Policy detected", () => ({
value: cspHeader
}));
}
const requestState = await resolvedClerkClient.authenticateRequest(
clerkRequest,
createAuthenticateRequestOptions(clerkRequest, options)
);
logger.debug("requestState", () => ({
status: requestState.status,
// @ts-expect-error : FIXME
headers: JSON.stringify(Object.fromEntries(requestState.headers)),
reason: requestState.reason
}));
const locationHeader = requestState.headers.get(constants.Headers.Location);
if (locationHeader) {
return new Response(null, { status: 307, headers: requestState.headers });
} else if (requestState.status === AuthStatus.Handshake) {
throw new Error("Clerk: handshake status without redirect");
}
const authObject = requestState.toAuth();
logger.debug("auth", () => ({ auth: authObject, debug: authObject.debug() }));
const redirectToSignIn = createMiddlewareRedirectToSignIn(clerkRequest);
const redirectToSignUp = createMiddlewareRedirectToSignUp(clerkRequest);
const protect = await createMiddlewareProtect(clerkRequest, authObject, redirectToSignIn);
const authHandler = createMiddlewareAuthHandler(authObject, redirectToSignIn, redirectToSignUp);
authHandler.protect = protect;
let handlerResult = NextResponse.next();
try {
const userHandlerResult = await clerkMiddlewareRequestDataStorage.run(
clerkMiddlewareRequestDataStore,
async () => handler == null ? void 0 : handler(authHandler, request2, event2)
);
handlerResult = userHandlerResult || handlerResult;
} catch (e) {
handlerResult = handleControlFlowErrors(e, clerkRequest, request2, requestState);
}
if (options.contentSecurityPolicy) {
const { headers } = createContentSecurityPolicyHeaders(
((_b = (_a = parsePublishableKey(publishableKey)) == null ? void 0 : _a.frontendApi) != null ? _b : "").replace("$", ""),
options.contentSecurityPolicy
);
headers.forEach(([key, value]) => {
setHeader(handlerResult, key, value);
});
logger.debug("Clerk generated CSP", () => ({
headers
}));
}
if (requestState.headers) {
requestState.headers.forEach((value, key) => {
if (key === constants.Headers.ContentSecurityPolicy) {
logger.debug("Content-Security-Policy detected", () => ({
value
}));
}
handlerResult.headers.append(key, value);
});
}
if (isRedirect(handlerResult)) {
logger.debug("handlerResult is redirect");
return serverRedirectWithAuth(clerkRequest, handlerResult, options);
}
if (options.debug) {
setRequestHeadersOnNextResponse(handlerResult, clerkRequest, { [constants.Headers.EnableDebug]: "true" });
}
const keylessKeysForRequestData = (
// Only pass keyless credentials when there are no explicit keys
secretKey === (keyless == null ? void 0 : keyless.secretKey) ? {
publishableKey: keyless == null ? void 0 : keyless.publishableKey,
secretKey: keyless == null ? void 0 : keyless.secretKey
} : {}
);
decorateRequest(clerkRequest, handlerResult, requestState, resolvedParams, keylessKeysForRequestData);
return handlerResult;
});
const keylessMiddleware = async (request2, event2) => {
var _a, _b;
if (isKeylessSyncRequest(request2)) {
return returnBackFromKeylessSync(request2);
}
const resolvedParams = typeof params === "function" ? await params(request2) : params;
const keyless = await getKeylessCookieValue((name) => {
var _a2;
return (_a2 = request2.cookies.get(name)) == null ? void 0 : _a2.value;
});
const isMissingPublishableKey = !(resolvedParams.publishableKey || PUBLISHABLE_KEY || (keyless == null ? void 0 : keyless.publishableKey));
const authHeader = (_b = (_a = getHeader(request2, constants.Headers.Authorization)) == null ? void 0 : _a.replace("Bearer ", "")) != null ? _b : "";
if (isMissingPublishableKey && !isMachineTokenByPrefix(authHeader)) {
const res = NextResponse.next();
setRequestHeadersOnNextResponse(res, request2, {
[constants.Headers.AuthStatus]: "signed-out"
});
return res;
}
return baseNextMiddleware(request2, event2);
};
const nextMiddleware = async (request2, event2) => {
if (canUseKeyless) {
return keylessMiddleware(request2, event2);
}
return baseNextMiddleware(request2, event2);
};
if (request && event) {
return nextMiddleware(request, event);
}
return nextMiddleware;
});
return middleware;
};
const parseRequestAndEvent = (args) => {
return [args[0] instanceof Request ? args[0] : void 0, args[0] instanceof Request ? args[1] : void 0];
};
const parseHandlerAndOptions = (args) => {
return [
typeof args[0] === "function" ? args[0] : void 0,
(args.length === 2 ? args[1] : typeof args[0] === "function" ? {} : args[0]) || {}
];
};
const isKeylessSyncRequest = (request) => request.nextUrl.pathname === "/clerk-sync-keyless";
const returnBackFromKeylessSync = (request) => {
const returnUrl = request.nextUrl.searchParams.get("returnUrl");
const url = new URL(request.url);
url.pathname = "";
return NextResponse.redirect(returnUrl || url.toString());
};
const createAuthenticateRequestOptions = (clerkRequest, options) => {
return {
...options,
...handleMultiDomainAndProxy(clerkRequest, options),
// TODO: Leaving the acceptsToken as 'any' opens up the possibility of
// an economic attack. We should revisit this and only verify a token
// when auth() or auth.protect() is invoked.
acceptsToken: "any"
};
};
const createMiddlewareRedirectToSignIn = (clerkRequest) => {
return (opts = {}) => {
const url = clerkRequest.clerkUrl.toString();
redirectToSignInError(url, opts.returnBackUrl);
};
};
const createMiddlewareRedirectToSignUp = (clerkRequest) => {
return (opts = {}) => {
const url = clerkRequest.clerkUrl.toString();
redirectToSignUpError(url, opts.returnBackUrl);
};
};
const createMiddlewareProtect = (clerkRequest, rawAuthObject, redirectToSignIn) => {
return async (params, options) => {
const notFound = () => nextjsNotFound();
const redirect = (url) => nextjsRedirectError(url, {
redirectUrl: url
});
const requestedToken = (params == null ? void 0 : params.token) || (options == null ? void 0 : options.token) || TokenType.SessionToken;
const authObject = getAuthObjectForAcceptedToken({ authObject: rawAuthObject, acceptsToken: requestedToken });
return createProtect({
request: clerkRequest,
redirect,
notFound,
unauthorized,
authObject,
redirectToSignIn
})(params, options);
};
};
const createMiddlewareAuthHandler = (rawAuthObject, redirectToSignIn, redirectToSignUp) => {
const authHandler = async (options) => {
var _a;
const acceptsToken = (_a = options == null ? void 0 : options.acceptsToken) != null ? _a : TokenType.SessionToken;
const authObject = getAuthObjectForAcceptedToken({ authObject: rawAuthObject, acceptsToken });
if (authObject.tokenType === TokenType.SessionToken && isTokenTypeAccepted(TokenType.SessionToken, acceptsToken)) {
return Object.assign(authObject, {
redirectToSignIn,
redirectToSignUp
});
}
return authObject;
};
return authHandler;
};
const handleControlFlowErrors = (e, clerkRequest, nextRequest, requestState) => {
var _a;
if (isNextjsUnauthorizedError(e)) {
const response = new NextResponse(null, { status: 401 });
const authObject = requestState.toAuth();
if (authObject && authObject.tokenType === TokenType.OAuthToken) {
const publishableKey = parsePublishableKey(requestState.publishableKey);
return setHeader(
response,
"WWW-Authenticate",
`Bearer resource_metadata="https://${publishableKey == null ? void 0 : publishableKey.frontendApi}/.well-known/oauth-protected-resource"`
);
}
return response;
}
if (isNextjsNotFoundError(e)) {
return setHeader(
// This is an internal rewrite purely to trigger a not found error. We do not want Next.js to think that the
// destination URL is a valid page, so we use `nextRequest.url` as the base for the fake URL, which Next.js
// understands is an internal URL and won't run middleware against the request.
NextResponse.rewrite(new URL(`/clerk_${Date.now()}`, nextRequest.url)),
constants.Headers.AuthReason,
"protect-rewrite"
);
}
const isRedirectToSignIn = isRedirectToSignInError(e);
const isRedirectToSignUp = isRedirectToSignUpError(e);
if (isRedirectToSignIn || isRedirectToSignUp) {
const redirect = createRedirect({
redirectAdapter,
baseUrl: clerkRequest.clerkUrl,
signInUrl: requestState.signInUrl,
signUpUrl: requestState.signUpUrl,
publishableKey: requestState.publishableKey,
sessionStatus: (_a = requestState.toAuth()) == null ? void 0 : _a.sessionStatus
});
const { returnBackUrl } = e;
return redirect[isRedirectToSignIn ? "redirectToSignIn" : "redirectToSignUp"]({ returnBackUrl });
}
if (isNextjsRedirectError(e)) {
return redirectAdapter(e.redirectUrl);
}
throw e;
};
export {
clerkMiddleware,
createAuthenticateRequestOptions
};
//# sourceMappingURL=clerkMiddleware.js.map