Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

Commit

Permalink
support search query in redirect URL (#23)
Browse files Browse the repository at this point in the history
* support search query in redirect URL

* commit file
  • Loading branch information
asafshen authored Mar 13, 2024
1 parent b17bc8d commit bfc749c
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default authMiddleware({

// The URL to redirect to if the user is not authenticated
// Defaults to process.env.SIGN_IN_ROUTE or '/sign-in' if not provided
// NOTE: In case it contains query parameters that exist in the original URL, they will override the original query parameters. e.g. if the original URL is /page?param1=1&param2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3&param2=2
redirectUrl?: string

// An array of public routes that do not require authentication
Expand Down
14 changes: 13 additions & 1 deletion src/server/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import descopeSdk from '@descope/node-sdk';
import type { AuthenticationInfo } from '@descope/node-sdk';
import { DEFAULT_PUBLIC_ROUTES, DESCOPE_SESSION_HEADER } from './constants';
import { getGlobalSdk } from './sdk';
import { mergeSearchParams } from './utils';

type MiddlewareOptions = {
// The Descope project ID to use for authentication
Expand All @@ -12,6 +13,7 @@ type MiddlewareOptions = {

// The URL to redirect to if the user is not authenticated
// Defaults to process.env.SIGN_IN_ROUTE or '/sign-in' if not provided
// NOTE: In case it contains query parameters that exist in the original URL, they will override the original query parameters. e.g. if the original URL is /page?param1=1&param2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3&param2=2
redirectUrl?: string;

// An array of public routes that do not require authentication
Expand Down Expand Up @@ -79,7 +81,17 @@ const createAuthMiddleware =
if (!isPublicRoute(req, options)) {
const redirectUrl = options.redirectUrl || DEFAULT_PUBLIC_ROUTES.signIn;
const url = req.nextUrl.clone();
url.pathname = redirectUrl;
// Create a URL object for redirectUrl. 'http://example.com' is just a placeholder.
const parsedRedirectUrl = new URL(redirectUrl, 'http://example.com');
url.pathname = parsedRedirectUrl.pathname;

const searchParams = mergeSearchParams(
url.search,
parsedRedirectUrl.search
);
if (searchParams) {
url.search = searchParams;
}
console.debug(`Auth middleware, Redirecting to ${redirectUrl}`);
return NextResponse.redirect(url);
}
Expand Down
1 change: 0 additions & 1 deletion src/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ declare const BUILD_VERSION: string;

export const DESCOPE_SESSION_HEADER = 'x-descope-session';

// eslint-disable-next-line import/prefer-default-export
export const baseHeaders = {
'x-descope-sdk-name': 'nextjs',
'x-descope-sdk-version': BUILD_VERSION
Expand Down
21 changes: 21 additions & 0 deletions src/server/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint-disable import/prefer-default-export */

/*
Merges multiple search params into one.
It will override according to the order of the search params
Examples:
- mergeSearchParams('?a=1', '?b=2') => 'a=1&b=2'
- mergeSearchParams('?a=1', '?a=2') => 'a=2'
- mergeSearchParams('?a=1', '?a=2', '?b=3') => 'a=2&b=3'
*/
export const mergeSearchParams = (...searchParams: string[]): string => {
const res = searchParams.reduce((acc, curr) => {
const currParams = new URLSearchParams(curr);
currParams.forEach((value, key) => {
acc.set(key, value);
});
return acc;
}, new URLSearchParams());

return res.toString();
};
13 changes: 13 additions & 0 deletions test/server/authMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,17 @@ describe('authMiddleware', () => {
pathname: customRedirectUrl
});
});

it('support and redirect url with search params', async () => {
mockValidateJwt.mockRejectedValue(new Error('Invalid JWT'));
const customRedirectUrl = '/custom-sign-in?redirect=/another-path';
const middleware = authMiddleware({ redirectUrl: customRedirectUrl });
const mockReq = createMockNextRequest({ pathname: '/private' });

await middleware(mockReq);
expect(NextResponse.redirect).toHaveBeenCalledWith({
pathname: '/custom-sign-in',
search: `redirect=${encodeURIComponent('/another-path')}`
});
});
});
15 changes: 15 additions & 0 deletions test/server/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { mergeSearchParams } from '../../src/server/utils';

describe('utils', () => {
describe('mergeSearchParams', () => {
it('should merge search params', () => {
const searchParams = mergeSearchParams('a=1&b=2', 'c=3&d=4');
expect(searchParams).toBe('a=1&b=2&c=3&d=4');
});

it('should merge search params with duplicate keys', () => {
const searchParams = mergeSearchParams('a=1&b=2', 'a=3&d=4');
expect(searchParams).toBe('a=3&b=2&d=4');
});
});
});

0 comments on commit bfc749c

Please sign in to comment.