From 107bdc59fc2f003daa77a2d3afd7271448f5f497 Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Mon, 22 Jul 2024 22:06:05 -0700 Subject: [PATCH 01/12] added private route array --- src/server/authMiddleware.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index afcd302..169e20f 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -25,6 +25,10 @@ type MiddlewareOptions = { // - 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 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 => { @@ -45,8 +49,9 @@ const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => { req.nextUrl.pathname ); const isPublic = options.publicRoutes?.includes(req.nextUrl.pathname); + const isPrivate = options.privateRoutes?.includes(req.nextUrl.pathname); - return isDefaultPublicRoute || isPublic; + return isDefaultPublicRoute || isPublic || !isPrivate; }; const addSessionToHeadersIfExists = ( From b4fecf3b0674ac9ddf271128ba3cde50f0577666 Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Mon, 22 Jul 2024 22:22:19 -0700 Subject: [PATCH 02/12] made it one or the other --- src/server/authMiddleware.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 169e20f..9d7bf87 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -19,17 +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¶m2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3¶m2=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 private routes that require authentication - // If privateRoutes is defined, routes not listed in this array will default to public routes - privateRoutes?: 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]; From 6889b469cb577072dd6d67e9b15a5a3ba1f6475d Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Tue, 23 Jul 2024 10:12:44 -0700 Subject: [PATCH 03/12] added runtime check for non-ts users --- src/server/authMiddleware.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 9d7bf87..5bb77bf 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -82,6 +82,12 @@ const createAuthMiddleware = async (req: NextRequest) => { console.debug('Auth middleware starts'); + if (options.publicRoutes && options.privateRoutes) { + throw new Error( + 'You can only define either publicRoutes or privateRoutes, not both.' + ); + } + const jwt = getSessionJwt(req); // check if the user is authenticated From 650d83644008f6498f97939dd52ff1e2e1baf44e Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Tue, 23 Jul 2024 10:56:04 -0700 Subject: [PATCH 04/12] add to README --- README.md | 13 ++++++++++--- src/server/authMiddleware.ts | 20 ++++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f602340..c7d5881 100644 --- a/README.md +++ b/README.md @@ -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¶m2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3¶m2=2 - redirectUrl?: string + redirectUrl?: string, // An array of public routes that do not require authentication + // All routes are private by default, this is where you will defined which routes are public. // 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 this is defined, then the default behavior of all routes being private will be changed, and all routes will be public by default. Private routes will therefore have to be controlled by this array + privateRoutes?: string[], + + // If you having privateRoutes and publicRoutes defined at the same time, privateRoutes by default will be ignored. }) export const config = { diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 5bb77bf..13aca6c 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -54,9 +54,20 @@ const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => { req.nextUrl.pathname ); const isPublic = options.publicRoutes?.includes(req.nextUrl.pathname); - const isPrivate = options.privateRoutes?.includes(req.nextUrl.pathname); - return isDefaultPublicRoute || isPublic || !isPrivate; + // If both publicRoutes and privateRoutes are provided, we prioritize publicRoutes + if (options.publicRoutes && options.privateRoutes) { + return isDefaultPublicRoute || isPublic; + } + + // If only publicRoutes are provided + if (options.publicRoutes) { + return isDefaultPublicRoute || isPublic; + } + + // If only privateRoutes are provided + const isPrivate = options.privateRoutes?.includes(req.nextUrl.pathname); + return isDefaultPublicRoute || !isPrivate; }; const addSessionToHeadersIfExists = ( @@ -83,9 +94,10 @@ const createAuthMiddleware = console.debug('Auth middleware starts'); if (options.publicRoutes && options.privateRoutes) { - throw new Error( - 'You can only define either publicRoutes or privateRoutes, not both.' + console.warn( + 'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.' ); + options.privateRoutes = undefined; } const jwt = getSessionJwt(req); From a9d67a58988b573b555cccb1427a8de0079902e4 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:56:47 -0700 Subject: [PATCH 05/12] Update README.md Co-authored-by: Asaf Shen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7d5881..c45fe00 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ export default authMiddleware({ // If this is defined, then the default behavior of all routes being private will be changed, and all routes will be public by default. Private routes will therefore have to be controlled by this array privateRoutes?: string[], - // If you having privateRoutes and publicRoutes defined at the same time, privateRoutes by default will be ignored. + // If you having privateRoutes and publicRoutes defined at the same time, privateRoutes will be ignored. }) export const config = { From b494d58047de17d9eaeacfba8d2df74284d5e6cf Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:56:59 -0700 Subject: [PATCH 06/12] Update README.md Co-authored-by: Asaf Shen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c45fe00..c2402ce 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ export default authMiddleware({ publicRoutes?: string[], // An array of private routes that require authentication - // If this is defined, then the default behavior of all routes being private will be changed, and all routes will be public by default. Private routes will therefore have to be controlled by this array + // 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. From 3153db1f39f8b69c63680eea753814f049226430 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:57:17 -0700 Subject: [PATCH 07/12] Update README.md Co-authored-by: Asaf Shen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2402ce..4ca5f94 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ export default authMiddleware({ redirectUrl?: string, // An array of public routes that do not require authentication - // All routes are private by default, this is where you will defined which routes are public. + // 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 From 21c2a2dd914d6103ed4aea62e5b9766cf0eb00e3 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:57:41 -0700 Subject: [PATCH 08/12] Update src/server/authMiddleware.ts Co-authored-by: Asaf Shen --- src/server/authMiddleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 13aca6c..0a78faa 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -93,7 +93,7 @@ const createAuthMiddleware = async (req: NextRequest) => { console.debug('Auth middleware starts'); - if (options.publicRoutes && options.privateRoutes) { + if (options.publicRoutes?.length > 0 && options.privateRoutes?.length > 0) { console.warn( 'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.' ); From 4e9ae5edc89d463e411d01b78b002304b7ee58a2 Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Fri, 26 Jul 2024 15:24:21 -0700 Subject: [PATCH 09/12] added support for empty array and also duplicate conditions --- src/server/authMiddleware.ts | 40 +++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 0a78faa..53ae76e 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -53,21 +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); - // If both publicRoutes and privateRoutes are provided, we prioritize publicRoutes - if (options.publicRoutes && options.privateRoutes) { - return isDefaultPublicRoute || isPublic; + if (options.publicRoutes && options.publicRoutes.length > 0) { + return ( + isDefaultPublicRoute || + options.publicRoutes.includes(req.nextUrl.pathname) + ); } - // If only publicRoutes are provided - if (options.publicRoutes) { - return isDefaultPublicRoute || isPublic; + if (options.privateRoutes && options.privateRoutes.length > 0) { + return ( + isDefaultPublicRoute || + !options.privateRoutes.includes(req.nextUrl.pathname) + ); } - // If only privateRoutes are provided - const isPrivate = options.privateRoutes?.includes(req.nextUrl.pathname); - return isDefaultPublicRoute || !isPrivate; + // If no routes are provided, all routes are public + return isDefaultPublicRoute; }; const addSessionToHeadersIfExists = ( @@ -93,26 +95,32 @@ const createAuthMiddleware = async (req: NextRequest) => { console.debug('Auth middleware starts'); - if (options.publicRoutes?.length > 0 && options.privateRoutes?.length > 0) { + const { publicRoutes, privateRoutes, ...restOptions } = options; + if (publicRoutes && privateRoutes) { console.warn( 'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.' ); - options.privateRoutes = undefined; } + const effectiveOptions = { + ...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'); From 3cd8e4041b158d1a0feac2ce51415ffa7cf52be1 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Sat, 27 Jul 2024 08:25:33 -0700 Subject: [PATCH 10/12] Update src/server/authMiddleware.ts Co-authored-by: Asaf Shen --- src/server/authMiddleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 53ae76e..d9a5473 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -61,7 +61,7 @@ const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => { ); } - if (options.privateRoutes && options.privateRoutes.length > 0) { + if (options.privateRoutes?.length > 0) { return ( isDefaultPublicRoute || !options.privateRoutes.includes(req.nextUrl.pathname) From 1c6e535a7906a7d9df3108f870b77e22a3b17047 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Sat, 27 Jul 2024 08:25:56 -0700 Subject: [PATCH 11/12] Update src/server/authMiddleware.ts Co-authored-by: Asaf Shen --- src/server/authMiddleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index d9a5473..2cafa19 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -54,7 +54,7 @@ const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => { req.nextUrl.pathname ); - if (options.publicRoutes && options.publicRoutes.length > 0) { + if (options.publicRoutes?.length > 0) { return ( isDefaultPublicRoute || options.publicRoutes.includes(req.nextUrl.pathname) From 93cd011eaba7a3f69604e8906f4818247e5d7131 Mon Sep 17 00:00:00 2001 From: Kevin Gao Date: Sat, 27 Jul 2024 08:50:27 -0700 Subject: [PATCH 12/12] changed logic and cleaned up createAuthMiddleware --- README.md | 45 +++++++++++++++++----- src/server/authMiddleware.ts | 73 +++++++++++++++--------------------- 2 files changed, 67 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 4ca5f94..7170cb8 100644 --- a/README.md +++ b/README.md @@ -147,17 +147,9 @@ export default authMiddleware({ // 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¶m2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3¶m2=2 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 + // These are the public and private routes in your app. Read more about how to use these below. 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. }) @@ -166,6 +158,41 @@ export const config = { } ``` +##### Public and Private Route Definitions + +- **All routes are private by default.** +- **`publicRoutes`:** Use this to specify which routes do not require authentication. If specified, only these routes and the default public routes will be public. +- **`privateRoutes`:** Use this to specify which routes require authentication. If specified, only these routes will be private, and all other routes will be public. +- **Conflict Handling:** If both `publicRoutes` and `privateRoutes` are provided, `privateRoutes` will be ignored, and a warning will be logged. + +This setup ensures that you can clearly define which routes in your application require authentication and which do not, while providing a mechanism to handle potential misconfigurations gracefully. + +###### Public Routes + +- **Description:** An array of public routes that do not require authentication. +- **Configuration:** Use `publicRoutes` to specify routes that don't require authentication. +- **Additional Defaults:** In addition to the routes you specify, the following default public routes are included: + - `process.env.SIGN_IN_ROUTE` or `/sign-in` if not provided + - `process.env.SIGN_UP_ROUTE` or `/sign-up` if not provided +- **Example:** + ```typescript + const options = { + publicRoutes: ['/home', '/about'] + }; + ``` + +###### Private Routes + +- **Description:** An array of private routes that require authentication. +- **Configuration:** Use `privateRoutes` to specify routes that require authentication. All other routes will be considered public. +- **Conflict Handling:** If both `publicRoutes` and `privateRoutes` are defined at the same time, `privateRoutes` will be ignored, and a warning will be logged. +- **Example:** + ```typescript + const options = { + privateRoutes: ['/dashboard', '/profile'] + }; + ``` + ##### Read session information in server side use the `session()` helper to read session information in Server Components and Route handlers. diff --git a/src/server/authMiddleware.ts b/src/server/authMiddleware.ts index 53ae76e..4ae4b70 100644 --- a/src/server/authMiddleware.ts +++ b/src/server/authMiddleware.ts @@ -19,22 +19,17 @@ 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¶m2=2 and the redirect URL is /sign-in?param1=3, the final redirect URL will be /sign-in?param1=3¶m2=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[]; - 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[]; - } -); + + // 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 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]; @@ -49,26 +44,33 @@ const getSessionJwt = (req: NextRequest): string | undefined => { return undefined; }; -const isPublicRoute = (req: NextRequest, options: MiddlewareOptions) => { +const isPublicRoute = ( + req: NextRequest, + publicRoutes: string[] = [], + privateRoutes: string[] = [] +) => { const isDefaultPublicRoute = Object.values(DEFAULT_PUBLIC_ROUTES).includes( req.nextUrl.pathname ); - if (options.publicRoutes && options.publicRoutes.length > 0) { - return ( - isDefaultPublicRoute || - options.publicRoutes.includes(req.nextUrl.pathname) + if (publicRoutes.length > 0 && privateRoutes.length > 0) { + console.warn( + 'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.' ); + return isDefaultPublicRoute || publicRoutes.includes(req.nextUrl.pathname); } - if (options.privateRoutes && options.privateRoutes.length > 0) { + if (publicRoutes.length > 0) { + return isDefaultPublicRoute || publicRoutes.includes(req.nextUrl.pathname); + } + + if (privateRoutes.length > 0) { return ( - isDefaultPublicRoute || - !options.privateRoutes.includes(req.nextUrl.pathname) + isDefaultPublicRoute || !privateRoutes.includes(req.nextUrl.pathname) ); } - // If no routes are provided, all routes are public + // If no routes are provided, all routes are private return isDefaultPublicRoute; }; @@ -95,32 +97,19 @@ const createAuthMiddleware = async (req: NextRequest) => { console.debug('Auth middleware starts'); - const { publicRoutes, privateRoutes, ...restOptions } = options; - if (publicRoutes && privateRoutes) { - console.warn( - 'Both publicRoutes and privateRoutes are defined. Ignoring privateRoutes.' - ); - } - - const effectiveOptions = { - ...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: effectiveOptions.projectId, - baseUrl: effectiveOptions.baseUrl + projectId: options.projectId, + baseUrl: options.baseUrl }).validateJwt(jwt); } catch (err) { console.debug('Auth middleware, Failed to validate JWT', err); - if (!isPublicRoute(req, effectiveOptions)) { - const redirectUrl = - effectiveOptions.redirectUrl || DEFAULT_PUBLIC_ROUTES.signIn; + if (!isPublicRoute(req, options.publicRoutes, options.privateRoutes)) { + const redirectUrl = options.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');