Skip to content

Commit

Permalink
fix(nextjs): url-based session syncing for auth() helpers (#3334)
Browse files Browse the repository at this point in the history
Co-authored-by: Lennart <lekoarts@gmail.com>
  • Loading branch information
BRKalow and LekoArts committed May 7, 2024
1 parent 39265d9 commit 4ae79af
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/late-readers-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/backend': patch
'@clerk/nextjs': patch
---

Pass `devBrowserToken` to `createRedirect()` to ensure methods from `auth()` that trigger redirects correctly pass the dev browser token for URL-based session syncing.
18 changes: 18 additions & 0 deletions packages/backend/src/__tests__/createRedirect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,5 +244,23 @@ export default (QUnit: QUnit) => {
),
);
});

test('passed dev browser when cross-origin redirect in dev', assert => {
const redirectAdapterSpy = sinon.spy(_url => 'redirectAdapterValue');
const { redirectToSignUp } = createRedirect({
baseUrl: 'http://www.clerk.com',
devBrowserToken: 'deadbeef',
redirectAdapter: redirectAdapterSpy,
publishableKey: 'pk_test_aW5jbHVkZWQua2F0eWRpZC05Mi5jbGVyay5hY2NvdW50cy5kZXYk',
});

const result = redirectToSignUp({ returnBackUrl });
assert.equal(result, 'redirectAdapterValue');
assert.ok(
redirectAdapterSpy.calledWith(
`https://included.katydid-92.accounts.dev/sign-up?redirect_url=${encodedUrl}&__clerk_db_jwt=deadbeef`,
),
);
});
});
};
25 changes: 21 additions & 4 deletions packages/backend/src/createRedirect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { constants } from './constants';
import { errorThrower, parsePublishableKey } from './util/shared';

const buildUrl = (_baseUrl: string | URL, _targetUrl: string | URL, _returnBackUrl?: string | URL | null) => {
const buildUrl = (
_baseUrl: string | URL,
_targetUrl: string | URL,
_returnBackUrl?: string | URL | null,
_devBrowserToken?: string | null,
) => {
if (_baseUrl === '') {
return legacyBuildUrl(_targetUrl.toString(), _returnBackUrl?.toString());
}
Expand All @@ -12,6 +18,10 @@ const buildUrl = (_baseUrl: string | URL, _targetUrl: string | URL, _returnBackU
if (returnBackUrl) {
res.searchParams.set('redirect_url', returnBackUrl.toString());
}
// For cross-origin redirects, we need to pass the dev browser token for URL session syncing
if (_devBrowserToken && baseUrl.hostname !== res.hostname) {
res.searchParams.set(constants.Cookies.DevBrowser, _devBrowserToken);
}
return res.toString();
};

Expand Down Expand Up @@ -66,6 +76,7 @@ export type RedirectFun<ReturnType> = (params?: RedirectToParams) => ReturnType;
*/
type CreateRedirect = <ReturnType>(params: {
publishableKey: string;
devBrowserToken?: string;
redirectAdapter: RedirectAdapter<ReturnType>;
baseUrl: URL | string;
signInUrl?: URL | string;
Expand All @@ -77,23 +88,29 @@ type CreateRedirect = <ReturnType>(params: {

export const createRedirect: CreateRedirect = params => {
const { publishableKey, redirectAdapter, signInUrl, signUpUrl, baseUrl } = params;
const frontendApi = parsePublishableKey(publishableKey)?.frontendApi;
const parsedPublishableKey = parsePublishableKey(publishableKey);
const frontendApi = parsedPublishableKey?.frontendApi;
const isDevelopment = parsedPublishableKey?.instanceType === 'development';
const accountsBaseUrl = buildAccountsBaseUrl(frontendApi);

const redirectToSignUp = ({ returnBackUrl }: RedirectToParams = {}) => {
if (!signUpUrl && !accountsBaseUrl) {
errorThrower.throwMissingPublishableKeyError();
}
const accountsSignUpUrl = `${accountsBaseUrl}/sign-up`;
return redirectAdapter(buildUrl(baseUrl, signUpUrl || accountsSignUpUrl, returnBackUrl));
return redirectAdapter(
buildUrl(baseUrl, signUpUrl || accountsSignUpUrl, returnBackUrl, isDevelopment ? params.devBrowserToken : null),
);
};

const redirectToSignIn = ({ returnBackUrl }: RedirectToParams = {}) => {
if (!signInUrl && !accountsBaseUrl) {
errorThrower.throwMissingPublishableKeyError();
}
const accountsSignInUrl = `${accountsBaseUrl}/sign-in`;
return redirectAdapter(buildUrl(baseUrl, signInUrl || accountsSignInUrl, returnBackUrl));
return redirectAdapter(
buildUrl(baseUrl, signInUrl || accountsSignInUrl, returnBackUrl, isDevelopment ? params.devBrowserToken : null),
);
};

return { redirectToSignUp, redirectToSignIn };
Expand Down
10 changes: 8 additions & 2 deletions packages/nextjs/src/app-router/server/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AuthObject, RedirectFun } from '@clerk/backend/internal';
import { createClerkRequest, createRedirect } from '@clerk/backend/internal';
import { constants, createClerkRequest, createRedirect } from '@clerk/backend/internal';
import { notFound, redirect } from 'next/navigation';

import { buildClerkProps } from '../../server/buildClerkProps';
Expand All @@ -23,9 +23,15 @@ export const auth = (): Auth => {
const clerkUrl = getAuthKeyFromRequest(request, 'ClerkUrl');

const redirectToSignIn: RedirectFun<never> = (opts = {}) => {
const clerkRequest = createClerkRequest(request);
const devBrowserToken =
clerkRequest.clerkUrl.searchParams.get(constants.Cookies.DevBrowser) ||
clerkRequest.cookies.get(constants.Cookies.DevBrowser);

return createRedirect({
redirectAdapter: redirect,
baseUrl: createClerkRequest(request).clerkUrl.toString(),
devBrowserToken: devBrowserToken,
baseUrl: clerkRequest.clerkUrl.toString(),
// TODO: Support runtime-value configuration of these options
// via setting and reading headers from clerkMiddleware
publishableKey: PUBLISHABLE_KEY,
Expand Down

0 comments on commit 4ae79af

Please sign in to comment.