Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/chatty-insects-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/nextjs': minor
'@clerk/shared': minor
---

Support reading from `__clerk_db_jwt` and `__dev_session` the dev browser jwt in development
2 changes: 1 addition & 1 deletion packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
noop,
parsePublishableKey,
proxyUrlToAbsoluteURL,
setDevBrowserJWTInURL,
stripScheme,
} from '@clerk/shared';
import type {
Expand Down Expand Up @@ -84,7 +85,6 @@ import {
removeClerkQueryParam,
requiresUserInput,
sessionExistsAndSingleSessionModeEnabled,
setDevBrowserJWTInURL,
stripOrigin,
stripSameOrigin,
toURL,
Expand Down
26 changes: 1 addition & 25 deletions packages/clerk-js/src/utils/__tests__/devbrowser.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,6 @@
import { getDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '../devBrowser';
import { getDevBrowserJWTFromURL } from '../devBrowser';

const DUMMY_URL_BASE = 'http://clerk-dummy';

describe('setDevBrowserJWTInURL(url, jwt)', () => {
const testCases: Array<[string, string, boolean, string]> = [
['', 'deadbeef', false, '#__clerk_db_jwt[deadbeef]'],
['foo', 'deadbeef', false, 'foo#__clerk_db_jwt[deadbeef]'],
['/foo', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
['#foo', 'deadbeef', false, '#foo__clerk_db_jwt[deadbeef]'],
['/foo?bar=42#qux', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
['/foo#__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
['/foo?bar=42#qux__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
['/foo', 'deadbeef', true, '/foo?__dev_session=deadbeef'],
['/foo?bar=42', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
];

test.each(testCases)(
'sets the dev browser JWT at the end of the provided url. Params: url=(%s), jwt=(%s), expected url=(%s)',
(input, paramName, asQueryParam, expected) => {
expect(setDevBrowserJWTInURL(new URL(input, DUMMY_URL_BASE), paramName, asQueryParam).href).toEqual(
new URL(expected, DUMMY_URL_BASE).href,
);
},
);
});

const oldHistory = globalThis.history;

describe('getDevBrowserJWTFromURL(url,)', () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/clerk-js/src/utils/cookies/devBrowser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { DEV_BROWSER_JWT_MARKER } from '@clerk/shared';
import { createCookieHandler } from '@clerk/shared/cookie';

import { DEV_BROWSER_JWT_MARKER } from '../devBrowser';

export const devBrowserCookie = createCookieHandler(DEV_BROWSER_JWT_MARKER);
53 changes: 4 additions & 49 deletions packages/clerk-js/src/utils/devBrowser.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,19 @@
import { DEV_BROWSER_SSO_JWT_PARAMETER } from '../core/constants';

export const DEV_BROWSER_JWT_MARKER = '__clerk_db_jwt';
const DEV_BROWSER_JWT_MARKER_REGEXP = /__clerk_db_jwt\[(.*)\]/;

// Sets the dev_browser JWT in the hash or the search
export function setDevBrowserJWTInURL(url: URL, jwt: string, asQueryParam: boolean): URL {
const resultURL = new URL(url);

// extract & strip existing jwt from hash
const jwtFromHash = extractDevBrowserJWTFromHash(resultURL.hash);
resultURL.hash = resultURL.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
if (resultURL.href.endsWith('#')) {
resultURL.hash = '';
}

// extract & strip existing jwt from search
const jwtFromSearch = resultURL.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER);
resultURL.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);

// Existing jwt takes precedence
const jwtToSet = jwtFromHash || jwtFromSearch || jwt;

if (jwtToSet) {
if (asQueryParam) {
resultURL.searchParams.append(DEV_BROWSER_SSO_JWT_PARAMETER, jwtToSet);
} else {
resultURL.hash = resultURL.hash + `${DEV_BROWSER_JWT_MARKER}[${jwtToSet}]`;
}
}

return resultURL;
}
import { extractDevBrowserJWTFromURLHash, extractDevBrowserJWTFromURLSearchParams } from '@clerk/shared';

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

// extract & strip existing jwt from hash
const jwtFromHash = extractDevBrowserJWTFromHash(resultURL.hash);
resultURL.hash = resultURL.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
if (resultURL.href.endsWith('#')) {
resultURL.hash = '';
}

// extract & strip existing jwt from search
const jwtFromSearch = resultURL.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER) || '';
resultURL.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);
const jwtFromHash = extractDevBrowserJWTFromURLHash(resultURL);
const jwtFromSearch = extractDevBrowserJWTFromURLSearchParams(resultURL);

const jwt = jwtFromHash || jwtFromSearch;

if (jwt && typeof globalThis.history !== undefined) {
if (jwt && typeof globalThis.history !== 'undefined') {
globalThis.history.replaceState(null, '', resultURL.href);
}

return jwt;
}

function extractDevBrowserJWTFromHash(hash: string): string {
const matches = hash.match(DEV_BROWSER_JWT_MARKER_REGEXP);
return matches ? matches[1] : '';
}
2 changes: 1 addition & 1 deletion packages/nextjs/src/server/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import type { AuthObject, RequestState } from '@clerk/backend';
import { buildRequestUrl, constants } from '@clerk/backend';
import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from '@clerk/shared/devBrowser';
import type Link from 'next/link';
import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
Expand All @@ -9,7 +10,6 @@ import { isRedirect, mergeResponses, paths, setHeader, stringifyHeaders } from '
import { withLogger } from '../utils/debugLogger';
import { authenticateRequest, handleInterstitialState, handleUnknownState } from './authenticateRequest';
import { SECRET_KEY } from './clerkClient';
import { DEV_BROWSER_JWT_MARKER, setDevBrowserJWTInURL } from './devBrowser';
import {
clockSkewDetected,
infiniteRedirectLoopDetected,
Expand Down
45 changes: 0 additions & 45 deletions packages/nextjs/src/server/devBrowser.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/shared/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ color
cookie
date
deprecated
devBrowser
error
file
globs
Expand Down
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"cookie",
"date",
"deprecated",
"devBrowser",
"error",
"file",
"globs",
Expand Down
28 changes: 28 additions & 0 deletions packages/shared/src/__tests__/devbrowser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { setDevBrowserJWTInURL } from '../devBrowser';

const DUMMY_URL_BASE = 'http://clerk-dummy';

describe('setDevBrowserJWTInURL(url, jwt)', () => {
const testCases: Array<[string, string, boolean, string]> = [
['', 'deadbeef', false, '#__clerk_db_jwt[deadbeef]'],
['foo', 'deadbeef', false, 'foo#__clerk_db_jwt[deadbeef]'],
['/foo', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
['#foo', 'deadbeef', false, '#foo__clerk_db_jwt[deadbeef]'],
['/foo?bar=42#qux', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
['/foo#__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo#__clerk_db_jwt[deadbeef]'],
['/foo?bar=42#qux__clerk_db_jwt[deadbeef]', 'deadbeef', false, '/foo?bar=42#qux__clerk_db_jwt[deadbeef]'],
['/foo', 'deadbeef', true, '/foo?__dev_session=deadbeef'],
['/foo?bar=42', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
['/foo?bar=42&__clerk_db_jwt=deadbeef', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
['/foo?bar=42&__dev_session=deadbeef', 'deadbeef', true, '/foo?bar=42&__dev_session=deadbeef'],
];

test.each(testCases)(
'sets the dev browser JWT at the end of the provided url. Params: url=(%s), jwt=(%s), expected url=(%s)',
(input, paramName, asQueryParam, expected) => {
expect(setDevBrowserJWTInURL(new URL(input, DUMMY_URL_BASE), paramName, asQueryParam).href).toEqual(
new URL(expected, DUMMY_URL_BASE).href,
);
},
);
});
56 changes: 56 additions & 0 deletions packages/shared/src/devBrowser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export const DEV_BROWSER_SSO_JWT_PARAMETER = '__dev_session';
export const DEV_BROWSER_JWT_MARKER = '__clerk_db_jwt';
const DEV_BROWSER_JWT_MARKER_REGEXP = /__clerk_db_jwt\[(.*)\]/;

// Sets the dev_browser JWT in the hash or the search
export function setDevBrowserJWTInURL(url: URL, jwt: string, asQueryParam: boolean): URL {
const resultURL = new URL(url);

const jwtFromHash = extractDevBrowserJWTFromURLHash(resultURL);
const jwtFromSearch = extractDevBrowserJWTFromURLSearchParams(resultURL);
// Existing jwt takes precedence
const jwtToSet = jwtFromHash || jwtFromSearch || jwt;

if (jwtToSet) {
if (asQueryParam) {
resultURL.searchParams.append(DEV_BROWSER_SSO_JWT_PARAMETER, jwtToSet);
} else {
resultURL.hash = resultURL.hash + `${DEV_BROWSER_JWT_MARKER}[${jwtToSet}]`;
}
}

return resultURL;
}

function extractDevBrowserJWTFromHash(hash: string): string {
const matches = hash.match(DEV_BROWSER_JWT_MARKER_REGEXP);
return matches ? matches[1] : '';
}

/**
* Extract & strip existing jwt from hash
* Side effect: Removes dev browser from the url hash
**/
export function extractDevBrowserJWTFromURLHash(url: URL) {
const jwt = extractDevBrowserJWTFromHash(url.hash);
url.hash = url.hash.replace(DEV_BROWSER_JWT_MARKER_REGEXP, '');
if (url.href.endsWith('#')) {
url.hash = '';
}

return jwt;
}

/**
* Extract & strip existing jwt from search params
* Side effect: Removes dev browser from the search params
**/
export function extractDevBrowserJWTFromURLSearchParams(url: URL) {
const jwtFromDevSession = url.searchParams.get(DEV_BROWSER_SSO_JWT_PARAMETER);
url.searchParams.delete(DEV_BROWSER_SSO_JWT_PARAMETER);

const jwtFromClerkDbJwt = url.searchParams.get(DEV_BROWSER_JWT_MARKER);
url.searchParams.delete(DEV_BROWSER_JWT_MARKER);

return jwtFromDevSession || jwtFromClerkDbJwt || '';
}
3 changes: 2 additions & 1 deletion packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

export * from './utils';

export { createWorkerTimers } from './workerTimers';
export * from './browser';
export { callWithRetry } from './callWithRetry';
export * from './color';
export * from './date';
export * from './deprecated';
export * from './devBrowser';
export * from './error';
export * from './file';
export { handleValueOrFn } from './handleValueOrFn';
Expand All @@ -27,3 +27,4 @@ export * from './poller';
export * from './proxy';
export * from './underscore';
export * from './url';
export { createWorkerTimers } from './workerTimers';
1 change: 1 addition & 0 deletions packages/shared/subpaths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const subpathNames = [
'cookie',
'date',
'deprecated',
'devBrowser',
'error',
'file',
'globs',
Expand Down