diff --git a/README.md b/README.md index f602340..7170cb8 100644 --- a/README.md +++ b/README.md @@ -140,18 +140,17 @@ 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 - // 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[] + // These are the public and private routes in your app. Read more about how to use these below. + publicRoutes?: string[], + privateRoutes?: string[], + // If you having privateRoutes and publicRoutes defined at the same time, privateRoutes will be ignored. }) export const config = { @@ -159,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 afcd302..4ae4b70 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 => { @@ -40,13 +44,34 @@ 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 ); - const isPublic = options.publicRoutes?.includes(req.nextUrl.pathname); - return isDefaultPublicRoute || isPublic; + 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 (publicRoutes.length > 0) { + return isDefaultPublicRoute || publicRoutes.includes(req.nextUrl.pathname); + } + + if (privateRoutes.length > 0) { + return ( + isDefaultPublicRoute || !privateRoutes.includes(req.nextUrl.pathname) + ); + } + + // If no routes are provided, all routes are private + return isDefaultPublicRoute; }; const addSessionToHeadersIfExists = ( @@ -83,7 +108,7 @@ const createAuthMiddleware = }).validateJwt(jwt); } catch (err) { console.debug('Auth middleware, Failed to validate JWT', err); - if (!isPublicRoute(req, options)) { + 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.