From 6ac16c769652a79c0dcc38b65400ed96846b4264 Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Mon, 28 Oct 2024 19:16:50 +0100 Subject: [PATCH 1/5] Add signUpPaths config --- README.md | 12 ++++++++++++ src/interfaces.ts | 1 + src/middleware.ts | 3 ++- src/session.ts | 23 ++++++++++++++++++++--- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a222684..8a6d607 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,18 @@ Use the `refreshSession` method in a server action or route handler to fetch the The `organizationId` parameter can be passed to `refreshSession` in order to switch the session to a different organization. If the current session is not authorized for the next organization, an appropriate [authentication error](https://workos.com/docs/reference/user-management/authentication-errors) will be returned. +### Sign up paths + +The `signUpPaths` option can be passed to `authkitMiddleware` to specify paths that should use the 'sign-up' screen hint when redirecting to AuthKit. This is useful for cases where you want a path that mandates authentication to be treated as a sign up page. + +```ts +import { authkitMiddleware } from '@workos-inc/authkit-nextjs'; + +export default authkitMiddleware({ + signUpPaths: ['/account/sign-up'], +}); +``` + ### Debugging To enable debug logs, initialize the middleware with the debug flag enabled. diff --git a/src/interfaces.ts b/src/interfaces.ts index 32d17e4..9583b32 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -57,6 +57,7 @@ export interface AuthkitMiddlewareOptions { debug?: boolean; middlewareAuth?: AuthkitMiddlewareAuth; redirectUri?: string; + signUpPaths?: string[]; } export interface CookieOptions { diff --git a/src/middleware.ts b/src/middleware.ts index 016d868..8474b31 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -7,8 +7,9 @@ export function authkitMiddleware({ debug = false, middlewareAuth = { enabled: false, unauthenticatedPaths: [] }, redirectUri = WORKOS_REDIRECT_URI, + signUpPaths = [], }: AuthkitMiddlewareOptions = {}): NextMiddleware { return function (request) { - return updateSession(request, debug, middlewareAuth, redirectUri); + return updateSession(request, debug, middlewareAuth, redirectUri, signUpPaths); }; } diff --git a/src/session.ts b/src/session.ts index 05d7203..21b3417 100644 --- a/src/session.ts +++ b/src/session.ts @@ -16,6 +16,7 @@ import { parse, tokensToRegexp } from 'path-to-regexp'; const sessionHeaderName = 'x-workos-session'; const middlewareHeaderName = 'x-workos-middleware'; const redirectUriHeaderName = 'x-redirect-uri'; +const signUpPathsHeaderName = 'x-sign-up-paths'; const JWKS = createRemoteJWKSet(new URL(workos.userManagement.getJwksUrl(WORKOS_CLIENT_ID))); @@ -28,6 +29,7 @@ async function updateSession( debug: boolean, middlewareAuth: AuthkitMiddlewareAuth, redirectUri: string, + signUpPaths: string[], ) { if (!redirectUri && !WORKOS_REDIRECT_URI) { throw new Error('You must provide a redirect URI in the AuthKit middleware or in the environment variables.'); @@ -44,6 +46,11 @@ async function updateSession( // Record that the request was routed through the middleware so we can check later for DX purposes newRequestHeaders.set(middlewareHeaderName, 'true'); + // Record the sign up paths so we can use it later + if (signUpPaths.length > 0) { + newRequestHeaders.set(signUpPathsHeaderName, signUpPaths.join(',')); + } + let url; // If the redirect URI is set, store it in the headers so we can use it later @@ -77,6 +84,8 @@ async function updateSession( return pathRegex.exec(request.nextUrl.pathname); }); + const screenHint = signUpPaths.includes(request.nextUrl.pathname) ? 'sign-up' : 'sign-in'; + // If the user is logged out and this path isn't on the allowlist for logged out paths, redirect to AuthKit. if (middlewareAuth.enabled && matchedPaths.length === 0 && !session) { if (debug) console.log(`Unauthenticated user on protected route ${request.url}, redirecting to AuthKit`); @@ -85,6 +94,7 @@ async function updateSession( await getAuthorizationUrl({ returnPathname: getReturnPathname(request.url), redirectUri: redirectUri ?? WORKOS_REDIRECT_URI, + screenHint, }), ); } @@ -227,10 +237,17 @@ function getMiddlewareAuthPathRegex(pathGlob: string) { async function redirectToSignIn() { const headersList = await headers(); - const url = headersList.get('x-url'); - const returnPathname = url ? getReturnPathname(url) : undefined; + const url = headersList.get('x-url') ?? ''; + + // Determine if the current route is in the sign up paths + const signUpPaths = headersList.get(signUpPathsHeaderName)?.split(','); + + const pathname = new URL(url).pathname; + const screenHint = signUpPaths?.includes(pathname) ? 'sign-up' : 'sign-in'; + + const returnPathname = url && getReturnPathname(url); - redirect(await getAuthorizationUrl({ returnPathname })); + redirect(await getAuthorizationUrl({ returnPathname, screenHint })); } async function withAuth(options?: { ensureSignedIn: false }): Promise; From 1faa251bc6873385bf2a41f7f664b14abe7472c3 Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Wed, 6 Nov 2024 11:51:16 +0100 Subject: [PATCH 2/5] Add regex logic and make it reusable --- src/session.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/session.ts b/src/session.ts index f1afe31..f6ff87b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -84,8 +84,6 @@ async function updateSession( return pathRegex.exec(request.nextUrl.pathname); }); - const screenHint = signUpPaths.includes(request.nextUrl.pathname) ? 'sign-up' : 'sign-in'; - // If the user is logged out and this path isn't on the allowlist for logged out paths, redirect to AuthKit. if (middlewareAuth.enabled && matchedPaths.length === 0 && !session) { if (debug) console.log(`Unauthenticated user on protected route ${request.url}, redirecting to AuthKit`); @@ -93,7 +91,7 @@ async function updateSession( const redirectTo = await getAuthorizationUrl({ returnPathname: getReturnPathname(request.url), redirectUri: redirectUri ?? WORKOS_REDIRECT_URI, - screenHint, + screenHint: getScreenHint(signUpPaths, request.nextUrl.pathname), }); // Fall back to standard Response if NextResponse is not available. @@ -252,7 +250,7 @@ async function redirectToSignIn() { const signUpPaths = headersList.get(signUpPathsHeaderName)?.split(','); const pathname = new URL(url).pathname; - const screenHint = signUpPaths?.includes(pathname) ? 'sign-up' : 'sign-in'; + const screenHint = getScreenHint(signUpPaths, pathname); const returnPathname = url && getReturnPathname(url); @@ -361,4 +359,19 @@ function getReturnPathname(url: string): string { return `${newUrl.pathname}${newUrl.searchParams.size > 0 ? '?' + newUrl.searchParams.toString() : ''}`; } +function getScreenHint(signUpPaths: string[] | string | undefined, pathname: string) { + if (!signUpPaths) return 'sign-in'; + + if (!Array.isArray(signUpPaths)) { + return signUpPaths.includes(pathname) ? 'sign-up' : 'sign-in'; + } + + const screenHintPaths: string[] = signUpPaths.filter((pathGlob) => { + const pathRegex = getMiddlewareAuthPathRegex(pathGlob); + return pathRegex.exec(pathname); + }); + + return screenHintPaths.length > 0 ? 'sign-up' : 'sign-in'; +} + export { encryptSession, withAuth, refreshSession, terminateSession, updateSession, getSession }; From c64814d402c068712b5096af8cdaa942160232dd Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Wed, 6 Nov 2024 11:52:48 +0100 Subject: [PATCH 3/5] Clarify in README that you can use glob logic --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a6d607..491925c 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ The `signUpPaths` option can be passed to `authkitMiddleware` to specify paths t import { authkitMiddleware } from '@workos-inc/authkit-nextjs'; export default authkitMiddleware({ - signUpPaths: ['/account/sign-up'], + signUpPaths: ['/account/sign-up', '/dashboard/:path*'], }); ``` From bbcb95ab59cbb46b328744e2506c9e9e4aceaee5 Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Fri, 8 Nov 2024 11:57:38 +0100 Subject: [PATCH 4/5] Run the regex on single strings as well --- src/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index f6ff87b..3bdf45c 100644 --- a/src/session.ts +++ b/src/session.ts @@ -363,7 +363,8 @@ function getScreenHint(signUpPaths: string[] | string | undefined, pathname: str if (!signUpPaths) return 'sign-in'; if (!Array.isArray(signUpPaths)) { - return signUpPaths.includes(pathname) ? 'sign-up' : 'sign-in'; + const pathRegex = getMiddlewareAuthPathRegex(signUpPaths); + return pathRegex.exec(pathname) ? 'sign-up' : 'sign-in'; } const screenHintPaths: string[] = signUpPaths.filter((pathGlob) => { From 8930bbd3104126f326fb63765c86744902b84085 Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Mon, 11 Nov 2024 15:07:19 +0100 Subject: [PATCH 5/5] Add sign up paths to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 80f3b18..ed19004 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ The middleware can be configured with several options. | `redirectUri` | `undefined` | Used in cases where you need your redirect URI to be set dynamically (e.g. Vercel preview deployments) | | `middlewareAuth` | `undefined` | Used to configure middleware auth options. See [middleware auth](#middleware-auth) for more details. | | `debug` | `false` | Enables debug logs. | +| `signUpPaths` | `[]` | Used to specify paths that should use the 'sign-up' screen hint when redirecting to AuthKit. | #### Custom redirect URI