Skip to content

Commit df40705

Browse files
authored
chore(clerk-js,shared,nextjs): Support __clerk_db_jwt and __dev_session query params (#2428)
* chore(clerk-js,shared,nextjs): Move `setDevBrowserJWTInURL` to shared * chore(shared): Support reading dev browser jwt from `__dev_session` and `__clerk_db_jwt` * chore(repo): Add changeset
1 parent a8feab7 commit df40705

File tree

13 files changed

+104
-124
lines changed

13 files changed

+104
-124
lines changed

.changeset/chatty-insects-run.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/nextjs': minor
4+
'@clerk/shared': minor
5+
---
6+
7+
Support reading from `__clerk_db_jwt` and `__dev_session` the dev browser jwt in development

packages/clerk-js/src/core/clerk.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
noop,
1313
parsePublishableKey,
1414
proxyUrlToAbsoluteURL,
15+
setDevBrowserJWTInURL,
1516
stripScheme,
1617
} from '@clerk/shared';
1718
import type {
@@ -84,7 +85,6 @@ import {
8485
removeClerkQueryParam,
8586
requiresUserInput,
8687
sessionExistsAndSingleSessionModeEnabled,
87-
setDevBrowserJWTInURL,
8888
stripOrigin,
8989
stripSameOrigin,
9090
toURL,

packages/clerk-js/src/utils/__tests__/devbrowser.test.ts

+1-25
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,6 @@
1-
import { getDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '../devBrowser';
1+
import { getDevBrowserJWTFromURL } from '../devBrowser';
22

33
const DUMMY_URL_BASE = 'http://clerk-dummy';
4-
5-
describe('setDevBrowserJWTInURL(url, jwt)', () => {
6-
const testCases: Array<[string, string, boolean, string]> = [
7-
['', 'deadbeef', false, '#__clerk_db_jwt[deadbeef]'],
8-
['foo', 'deadbeef', false, 'foo#__clerk_db_jwt[deadbeef]'],
9-
['/foo', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
10-
['#foo', 'deadbeef', false, '#foo__clerk_db_jwt[deadbeef]'],
11-
['/foo?bar=42#qux', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
12-
['/foo#__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
13-
['/foo?bar=42#qux__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
14-
['/foo', 'deadbeef', true, '/foo?__dev_session=deadbeef'],
15-
['/foo?bar=42', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
16-
];
17-
18-
test.each(testCases)(
19-
'sets the dev browser JWT at the end of the provided url. Params: url=(%s), jwt=(%s), expected url=(%s)',
20-
(input, paramName, asQueryParam, expected) => {
21-
expect(setDevBrowserJWTInURL(new URL(input, DUMMY_URL_BASE), paramName, asQueryParam).href).toEqual(
22-
new URL(expected, DUMMY_URL_BASE).href,
23-
);
24-
},
25-
);
26-
});
27-
284
const oldHistory = globalThis.history;
295

306
describe('getDevBrowserJWTFromURL(url,)', () => {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1+
import { DEV_BROWSER_JWT_MARKER } from '@clerk/shared';
12
import { createCookieHandler } from '@clerk/shared/cookie';
23

3-
import { DEV_BROWSER_JWT_MARKER } from '../devBrowser';
4-
54
export const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_MARKER);
+4-49
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,19 @@
1-
import { DEV_BROWSER_SSO_JWT_PARAMETER } from '../core/constants';
2-
3-
export const DEV_BROWSER_JWT_MARKER = '__clerk_db_jwt';
4-
const DEV_BROWSER_JWT_MARKER_REGEXP = /__clerk_db_jwt\[(.*)\]/;
5-
6-
// Sets the dev_browser JWT in the hash or the search
7-
export function setDevBrowserJWTInURL(url: URL, jwt: string, asQueryParam: boolean): URL {
8-
const resultURL = new URL(url);
9-
10-
// extract & strip existing jwt from hash
11-
const jwtFromHash = extractDevBrowserJWTFromHash(resultURL.hash);
12-
resultURL.hash = resultURL.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
13-
if (resultURL.href.endsWith('#')) {
14-
resultURL.hash = '';
15-
}
16-
17-
// extract & strip existing jwt from search
18-
const jwtFromSearch = resultURL.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER);
19-
resultURL.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);
20-
21-
// Existing jwt takes precedence
22-
const jwtToSet = jwtFromHash || jwtFromSearch || jwt;
23-
24-
if (jwtToSet) {
25-
if (asQueryParam) {
26-
resultURL.searchParams.append(DEV_BROWSER_SSO_JWT_PARAMETER, jwtToSet);
27-
} else {
28-
resultURL.hash = resultURL.hash + `${DEV_BROWSER_JWT_MARKER}[${jwtToSet}]`;
29-
}
30-
}
31-
32-
return resultURL;
33-
}
1+
import { extractDevBrowserJWTFromURLHash, extractDevBrowserJWTFromURLSearchParams } from '@clerk/shared';
342

353
// Gets the dev_browser JWT from either the hash or the search
364
// Side effect:
375
// Removes dev_browser JWT from the URL as a side effect and updates the browser history
386
export function getDevBrowserJWTFromURL(url: URL): string {
397
const resultURL = new URL(url);
408

41-
// extract & strip existing jwt from hash
42-
const jwtFromHash = extractDevBrowserJWTFromHash(resultURL.hash);
43-
resultURL.hash = resultURL.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
44-
if (resultURL.href.endsWith('#')) {
45-
resultURL.hash = '';
46-
}
47-
48-
// extract & strip existing jwt from search
49-
const jwtFromSearch = resultURL.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER) || '';
50-
resultURL.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);
9+
const jwtFromHash = extractDevBrowserJWTFromURLHash(resultURL);
10+
const jwtFromSearch = extractDevBrowserJWTFromURLSearchParams(resultURL);
5111

5212
const jwt = jwtFromHash || jwtFromSearch;
5313

54-
if (jwt && typeof globalThis.history !== undefined) {
14+
if (jwt && typeof globalThis.history !== 'undefined') {
5515
globalThis.history.replaceState(null, '', resultURL.href);
5616
}
5717

5818
return jwt;
5919
}
60-
61-
function extractDevBrowserJWTFromHash(hash: string): string {
62-
const matches = hash.match(DEV_BROWSER_JWT_MARKER_REGEXP);
63-
return matches ? matches[1] : '';
64-
}

packages/nextjs/src/server/authMiddleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable turbo/no-undeclared-env-vars */
22
import type { AuthObject, RequestState } from '@clerk/backend';
33
import { buildRequestUrl, constants } from '@clerk/backend';
4+
import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser';
45
import type Link from 'next/link';
56
import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
67
import { NextResponse } from 'next/server';
@@ -9,7 +10,6 @@ import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from '
910
import { withLogger } from '../utils/debugLogger';
1011
import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest';
1112
import { SECRET_KEY } from './clerkClient';
12-
import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser';
1313
import {
1414
clockSkewDetected,
1515
infiniteRedirectLoopDetected,

packages/nextjs/src/server/devBrowser.ts

-45
This file was deleted.

packages/shared/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ color
44
cookie
55
date
66
deprecated
7+
devBrowser
78
error
89
file
910
globs

packages/shared/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"cookie",
5252
"date",
5353
"deprecated",
54+
"devBrowser",
5455
"error",
5556
"file",
5657
"globs",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { setDevBrowserJWTInURL } from '../devBrowser';
2+
3+
const DUMMY_URL_BASE = 'http://clerk-dummy';
4+
5+
describe('setDevBrowserJWTInURL(url, jwt)', () => {
6+
const testCases: Array<[string, string, boolean, string]> = [
7+
['', 'deadbeef', false, '#__clerk_db_jwt[deadbeef]'],
8+
['foo', 'deadbeef', false, 'foo#__clerk_db_jwt[deadbeef]'],
9+
['/foo', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
10+
['#foo', 'deadbeef', false, '#foo__clerk_db_jwt[deadbeef]'],
11+
['/foo?bar=42#qux', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
12+
['/foo#__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
13+
['/foo?bar=42#qux__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
14+
['/foo', 'deadbeef', true, '/foo?__dev_session=deadbeef'],
15+
['/foo?bar=42', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
16+
['/foo?bar=42&__clerk_db_jwt=deadbeef', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
17+
['/foo?bar=42&__dev_session=deadbeef', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
18+
];
19+
20+
test.each(testCases)(
21+
'sets the dev browser JWT at the end of the provided url. Params: url=(%s), jwt=(%s), expected url=(%s)',
22+
(input, paramName, asQueryParam, expected) => {
23+
expect(setDevBrowserJWTInURL(new URL(input, DUMMY_URL_BASE), paramName, asQueryParam).href).toEqual(
24+
new URL(expected, DUMMY_URL_BASE).href,
25+
);
26+
},
27+
);
28+
});

packages/shared/src/devBrowser.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export const DEV_BROWSER_SSO_JWT_PARAMETER = '__dev_session';
2+
export const DEV_BROWSER_JWT_MARKER = '__clerk_db_jwt';
3+
const DEV_BROWSER_JWT_MARKER_REGEXP = /__clerk_db_jwt\[(.*)\]/;
4+
5+
// Sets the dev_browser JWT in the hash or the search
6+
export function setDevBrowserJWTInURL(url: URL, jwt: string, asQueryParam: boolean): URL {
7+
const resultURL = new URL(url);
8+
9+
const jwtFromHash = extractDevBrowserJWTFromURLHash(resultURL);
10+
const jwtFromSearch = extractDevBrowserJWTFromURLSearchParams(resultURL);
11+
// Existing jwt takes precedence
12+
const jwtToSet = jwtFromHash || jwtFromSearch || jwt;
13+
14+
if (jwtToSet) {
15+
if (asQueryParam) {
16+
resultURL.searchParams.append(DEV_BROWSER_SSO_JWT_PARAMETER, jwtToSet);
17+
} else {
18+
resultURL.hash = resultURL.hash + `${DEV_BROWSER_JWT_MARKER}[${jwtToSet}]`;
19+
}
20+
}
21+
22+
return resultURL;
23+
}
24+
25+
function extractDevBrowserJWTFromHash(hash: string): string {
26+
const matches = hash.match(DEV_BROWSER_JWT_MARKER_REGEXP);
27+
return matches ? matches[1] : '';
28+
}
29+
30+
/**
31+
* Extract & strip existing jwt from hash
32+
* Side effect: Removes dev browser from the url hash
33+
**/
34+
export function extractDevBrowserJWTFromURLHash(url: URL) {
35+
const jwt = extractDevBrowserJWTFromHash(url.hash);
36+
url.hash = url.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
37+
if (url.href.endsWith('#')) {
38+
url.hash = '';
39+
}
40+
41+
return jwt;
42+
}
43+
44+
/**
45+
* Extract & strip existing jwt from search params
46+
* Side effect: Removes dev browser from the search params
47+
**/
48+
export function extractDevBrowserJWTFromURLSearchParams(url: URL) {
49+
const jwtFromDevSession = url.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER);
50+
url.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);
51+
52+
const jwtFromClerkDbJwt = url.searchParams.get(DEV_BROWSER_JWT_MARKER);
53+
url.searchParams.delete(DEV_BROWSER_JWT_MARKER);
54+
55+
return jwtFromDevSession || jwtFromClerkDbJwt || '';
56+
}

packages/shared/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010

1111
export * from './utils';
1212

13-
export { createWorkerTimers } from './workerTimers';
1413
export * from './browser';
1514
export { callWithRetry } from './callWithRetry';
1615
export * from './color';
1716
export * from './date';
1817
export * from './deprecated';
18+
export * from './devBrowser';
1919
export * from './error';
2020
export * from './file';
2121
export { handleValueOrFn } from './handleValueOrFn';
@@ -27,3 +27,4 @@ export * from './poller';
2727
export * from './proxy';
2828
export * from './underscore';
2929
export * from './url';
30+
export { createWorkerTimers } from './workerTimers';

packages/shared/subpaths.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const subpathNames = [
88
'cookie',
99
'date',
1010
'deprecated',
11+
'devBrowser',
1112
'error',
1213
'file',
1314
'globs',

0 commit comments

Comments
 (0)