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

Added Private Route Array #69

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,25 @@ import { authMiddleware } from '@descope/nextjs-sdk/server'
export default authMiddleware({
// The Descope project ID to use for authentication
// Defaults to process.env.DESCOPE_PROJECT_ID
projectId: 'your-descope-project-id'
projectId: 'your-descope-project-id',

// 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
redirectUrl?: string,

// An array of public routes that do not require authentication
// All routes are private by default, use this to configure routes that don't require authentication
// In addition to the default public routes:
// - process.env.SIGN_IN_ROUTE or /sign-in if not provided
// - process.env.SIGN_UP_ROUTE or /sign-up if not provided
publicRoutes?: string[]
publicRoutes?: string[],

// An array of private routes that require authentication
// If defined, the specified route will require authentication and the rest of the routes be will be public
privateRoutes?: string[],

// If you having privateRoutes and publicRoutes defined at the same time, privateRoutes will be ignored.
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
})

export const config = {
Expand Down
62 changes: 49 additions & 13 deletions src/server/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ type MiddlewareOptions = {
// 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
// In addition to the default public routes:
// - process.env.SIGN_IN_ROUTE or /sign-in if not provided
// - process.env.SIGN_UP_ROUTE or /sign-up if not provided
publicRoutes?: string[];
};
} & (
| {
// An array of public routes that do not require authentication
// In addition to the default public routes:
// - process.env.SIGN_IN_ROUTE or /sign-in if not provided
// - process.env.SIGN_UP_ROUTE or /sign-up if not provided
publicRoutes?: string[];
privateRoutes?: never;
}
| {
publicRoutes?: never;
// An array of private routes that require authentication
// If privateRoutes is defined, routes not listed in this array will default to public routes
privateRoutes?: string[];
}
);

const getSessionJwt = (req: NextRequest): string | undefined => {
let jwt = req.headers?.get('Authorization')?.split(' ')[1];
Expand All @@ -44,9 +53,23 @@ const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => {
const isDefaultPublicRoute = Object.values(DEFAULT_PUBLIC_ROUTES).includes(
req.nextUrl.pathname
);
const isPublic = options.publicRoutes?.includes(req.nextUrl.pathname);

return isDefaultPublicRoute || isPublic;
if (options.publicRoutes && options.publicRoutes.length > 0) {
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
return (
isDefaultPublicRoute ||
options.publicRoutes.includes(req.nextUrl.pathname)
);
}

if (options.privateRoutes && options.privateRoutes.length > 0) {
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
return (
isDefaultPublicRoute ||
!options.privateRoutes.includes(req.nextUrl.pathname)
);
}

// If no routes are provided, all routes are public
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
return isDefaultPublicRoute;
};

const addSessionToHeadersIfExists = (
Expand All @@ -72,19 +95,32 @@ const createAuthMiddleware =
async (req: NextRequest) => {
console.debug('Auth middleware starts');

const { publicRoutes, privateRoutes, ...restOptions } = options;
if (publicRoutes && privateRoutes) {
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
console.warn(
'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.'
);
}

const effectiveOptions = {
gaokevin1 marked this conversation as resolved.
Show resolved Hide resolved
...restOptions,
publicRoutes: publicRoutes && privateRoutes ? publicRoutes : publicRoutes
};

const jwt = getSessionJwt(req);

// check if the user is authenticated
let session: AuthenticationInfo | undefined;
try {
session = await getGlobalSdk({
projectId: options.projectId,
baseUrl: options.baseUrl
projectId: effectiveOptions.projectId,
baseUrl: effectiveOptions.baseUrl
}).validateJwt(jwt);
} catch (err) {
console.debug('Auth middleware, Failed to validate JWT', err);
if (!isPublicRoute(req, options)) {
const redirectUrl = options.redirectUrl || DEFAULT_PUBLIC_ROUTES.signIn;
if (!isPublicRoute(req, effectiveOptions)) {
const redirectUrl =
effectiveOptions.redirectUrl || DEFAULT_PUBLIC_ROUTES.signIn;
const url = req.nextUrl.clone();
// Create a URL object for redirectUrl. 'http://example.com' is just a placeholder.
const parsedRedirectUrl = new URL(redirectUrl, 'http://example.com');
Expand Down
Loading