Skip to content

Commit

Permalink
fix(nextjs): Introduce CLERK_USE_X_FWD_HEADERS and req.clerkUrl
Browse files Browse the repository at this point in the history
  • Loading branch information
nikosdouvlis committed Jun 20, 2023
1 parent 0a65a38 commit 56dc3e3
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 30 deletions.
75 changes: 45 additions & 30 deletions packages/nextjs/src/server/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NextResponse } from 'next/server';
import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from '../utils';
import { withLogger } from '../utils/debugLogger';
import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest';
import { SECRET_KEY } from './clerkClient';
import { SECRET_KEY, USE_X_FWD_HEADERS } from './clerkClient';
import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser';
import { receivedRequestForIgnoredRoute } from './errors';
import { redirectToSignIn } from './redirect';
Expand Down Expand Up @@ -61,14 +61,16 @@ type RouteMatcherParam =
type IgnoredRoutesParam = Array<RegExp | string> | RegExp | string | ((req: NextRequest) => boolean);
type ApiRoutesParam = IgnoredRoutesParam;

type NextRequestWithClerkUrl = NextRequest & { experimental_clerkUrl: NextRequest['nextUrl'] };

type BeforeAuthHandler = (
req: NextRequest,
req: NextRequestWithClerkUrl,
evt: NextFetchEvent,
) => NextMiddlewareResult | Promise<NextMiddlewareResult> | false | Promise<false>;

type AfterAuthHandler = (
auth: AuthObject & { isPublicRoute: boolean; isApiRoute: boolean },
req: NextRequest,
req: NextRequestWithClerkUrl,
evt: NextFetchEvent,
) => NextMiddlewareResult | Promise<NextMiddlewareResult>;

Expand Down Expand Up @@ -120,6 +122,22 @@ export interface AuthMiddleware {
(params?: AuthMiddlewareParams): NextMiddleware;
}

const getFirstValueFromHeader = (req: NextRequest, key: string) => {
const value = req.headers.get(key);
return value?.split(',')[0];
};

const withNormalizedClerkUrl = (req: NextRequest): NextRequestWithClerkUrl => {
if (!USE_X_FWD_HEADERS) {
return Object.assign(req, { experimental_clerkUrl: req.nextUrl });
}
const clerkUrl = req.nextUrl.clone();
clerkUrl.protocol = getFirstValueFromHeader(req, constants.Headers.ForwardedProto) ?? clerkUrl.protocol;
clerkUrl.host = getFirstValueFromHeader(req, constants.Headers.ForwardedHost) ?? clerkUrl.host;
clerkUrl.port = getFirstValueFromHeader(req, constants.Headers.ForwardedPort) ?? clerkUrl.port;
return Object.assign(req, { experimental_clerkUrl: clerkUrl });
};

const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
const [params = {}] = args as [AuthMiddlewareParams?];
const { beforeAuth, afterAuth, publicRoutes, ignoredRoutes, apiRoutes, ...options } = params;
Expand All @@ -129,20 +147,26 @@ const authMiddleware: AuthMiddleware = (...args: unknown[]) => {
const isApiRoute = createApiRoutes(apiRoutes);
const defaultAfterAuth = createDefaultAfterAuth(isPublicRoute, isApiRoute);

return withLogger('authMiddleware', logger => async (req: NextRequest, evt: NextFetchEvent) => {
return withLogger('authMiddleware', logger => async (_req: NextRequest, evt: NextFetchEvent) => {
if (options.debug) {
logger.enable();
}

setClerkUrlInRequest(req);
const requestUrl = process.env.CLERK_USE_X_FWD_HEADERS !== 'true' ? req.clerkUrl : req.nextUrl;

logger.debug('URL debug', { url: req.nextUrl.href, method: req.method, headers: stringifyHeaders(req.headers) });
const req = withNormalizedClerkUrl(_req);

logger.debug('URL debug', {
url: req.nextUrl.href,
method: req.method,
headers: stringifyHeaders(req.headers),
nextUrl: req.nextUrl.href,
clerkUrl: req.experimental_clerkUrl.href,
});
logger.debug('Options debug', { ...options, beforeAuth: !!beforeAuth, afterAuth: !!afterAuth });

if (isIgnoredRoute(req)) {
logger.debug({ isIgnoredRoute: true });
console.warn(receivedRequestForIgnoredRoute(req.nextUrl.href, JSON.stringify(DEFAULT_CONFIG_MATCHER)));
console.warn(
receivedRequestForIgnoredRoute(req.experimental_clerkUrl.href, JSON.stringify(DEFAULT_CONFIG_MATCHER)),
);
return setHeader(NextResponse.next(), constants.Headers.AuthReason, 'ignored-route');
}

Expand Down Expand Up @@ -214,11 +238,11 @@ const createDefaultAfterAuth = (
isPublicRoute: ReturnType<typeof createRouteMatcher>,
isApiRoute: ReturnType<typeof createApiRoutes>,
) => {
return (auth: AuthObject, req: NextRequest) => {
return (auth: AuthObject, req: NextRequestWithClerkUrl) => {
if (!auth.userId && !isPublicRoute(req) && isApiRoute(req)) {
return apiEndpointUnauthorizedNextResponse();
} else if (!auth.userId && !isPublicRoute(req)) {
return redirectToSignIn({ returnBackUrl: req.nextUrl.href });
return redirectToSignIn({ returnBackUrl: req.experimental_clerkUrl.href });
}
return NextResponse.next();
};
Expand Down Expand Up @@ -256,10 +280,15 @@ const withDefaultPublicRoutes = (publicRoutes: RouteMatcherParam | undefined) =>

// Grabs the dev browser JWT from cookies and appends it to the redirect URL when redirecting to cross-origin.
// Middleware runs on the server side, before clerk-js is loaded, that's why we need Cookies.
const appendDevBrowserOnCrossOrigin = (req: NextRequest, res: Response) => {
const appendDevBrowserOnCrossOrigin = (req: NextRequestWithClerkUrl, res: Response) => {
const location = res.headers.get('location');
const shouldAppendDevBrowser = res.headers.get(constants.Headers.ClerkRedirectTo) === 'true';
if (shouldAppendDevBrowser && !!location && isDevelopmentFromApiKey(SECRET_KEY) && isCrossOrigin(req.url, location)) {
if (
shouldAppendDevBrowser &&
!!location &&
isDevelopmentFromApiKey(SECRET_KEY) &&
isCrossOrigin(req.experimental_clerkUrl, location)
) {
const dbJwt = req.cookies.get(DEV_BROWSER_JWT_MARKER)?.value;
const urlWithDevBrowser = setDevBrowserJWTInURL(location, dbJwt);
return NextResponse.redirect(urlWithDevBrowser, res);
Expand All @@ -275,12 +304,12 @@ const appendDevBrowserOnCrossOrigin = (req: NextRequest, res: Response) => {
//
// - If the user has provided a specific `apiRoutes` prop in `authMiddleware` then all the above are discarded,
// and only routes that match the user’s provided paths are considered API routes.
const createApiRoutes = (apiRoutes: RouteMatcherParam | undefined): ((req: NextRequest) => boolean) => {
const createApiRoutes = (apiRoutes: RouteMatcherParam | undefined): ((req: NextRequestWithClerkUrl) => boolean) => {
if (apiRoutes) {
return createRouteMatcher(apiRoutes);
}
const isDefaultApiRoute = createRouteMatcher(DEFAULT_API_ROUTES);
return (req: NextRequest) =>
return (req: NextRequestWithClerkUrl) =>
isDefaultApiRoute(req) || isRequestMethodIndicatingApiRoute(req) || isRequestContentTypeJson(req);
};

Expand Down Expand Up @@ -335,17 +364,3 @@ A bug that may have already been fixed in the latest version of Clerk NextJS pac
How to resolve:
-> Make sure you are using the latest version of '@clerk/nextjs' and 'next'.
`;

const setClerkUrlInRequest = (req: NextRequest) => {
const clerkUrl = req.nextUrl.clone();
clerkUrl.protocol = getFirstValueFromHeader(req, 'x-forwarded-proto') ?? clerkUrl.protocol;
clerkUrl.host = getFirstValueFromHeader(req, 'x-forwarded-host') ?? clerkUrl.host;
clerkUrl.port = getFirstValueFromHeader(req, 'x-forwarded-port') ?? clerkUrl.port;

Object.assign(req, { clerkUrl });
};

const getFirstValueFromHeader = (req: NextRequest, key: string) => {
const value = req.headers.get(key);
return value?.split(',')[0];
};
1 change: 1 addition & 0 deletions packages/nextjs/src/server/clerkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const PROXY_URL = process.env.NEXT_PUBLIC_CLERK_PROXY_URL || '';
export const IS_SATELLITE = process.env.NEXT_PUBLIC_CLERK_IS_SATELLITE === 'true' || false;
export const SIGN_IN_URL = process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL || '';
export const SIGN_UP_URL = process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL || '';
export const USE_X_FWD_HEADERS = process.env.CLERK_USE_X_FWD_HEADERS === 'true';

const clerkClient = Clerk({
apiKey: API_KEY,
Expand Down

0 comments on commit 56dc3e3

Please sign in to comment.