From a087499c6850b6bc7bb3c9a745577966930def8b Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Mon, 23 Dec 2024 16:13:49 +0200 Subject: [PATCH 1/3] Only redirect in middleware auth mode --- src/session.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/session.ts b/src/session.ts index f9c41af..c984606 100644 --- a/src/session.ts +++ b/src/session.ts @@ -166,18 +166,20 @@ async function updateSession( nextCookies.delete(cookieName); } - // If we get here, the session is invalid and the user needs to sign in again. - // We redirect to the current URL which will trigger the middleware again. - // This is outside of the above block because you cannot redirect in Next.js - // from inside a try/catch block. - return NextResponse?.redirect - ? NextResponse.redirect(request.url) - : new Response(null, { - status: 307, - headers: { - Location: request.url, - }, - }); + if (middlewareAuth.enabled) { + // If we get here, the session is invalid and the user needs to sign in again because we're using middleware auth mode. + // We redirect to the current URL which will trigger the middleware again. + // This is outside of the above block because you cannot redirect in Next.js + // from inside a try/catch block. + return NextResponse?.redirect + ? NextResponse.redirect(request.url) + : new Response(null, { + status: 307, + headers: { + Location: request.url, + }, + }); + } } async function refreshSession(options: { From 77958953d29caaaaafc7c02a78f35d23ffdd393c Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Mon, 23 Dec 2024 16:48:22 +0200 Subject: [PATCH 2/3] Return a response if not in middleware auth mode --- src/session.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index c984606..2ba6706 100644 --- a/src/session.ts +++ b/src/session.ts @@ -161,7 +161,7 @@ async function updateSession( response.cookies.set(cookieName, encryptedSession, getCookieOptions(redirectUri)); return response; } catch (e) { - if (debug) console.log('Failed to refresh. Deleting cookie and redirecting.', e); + if (debug) console.log('Failed to refresh. Deleting cookie.', e); nextCookies.delete(cookieName); } @@ -171,6 +171,7 @@ async function updateSession( // We redirect to the current URL which will trigger the middleware again. // This is outside of the above block because you cannot redirect in Next.js // from inside a try/catch block. + if (debug) console.log('Redirecting to AuthKit to log in again.'); return NextResponse?.redirect ? NextResponse.redirect(request.url) : new Response(null, { @@ -180,6 +181,13 @@ async function updateSession( }, }); } + + // If we aren't in middleware auth mode, we return a response and let the page handle what to do next. + const response = NextResponse.next({ + request: { headers: newRequestHeaders }, + }); + + return response; } async function refreshSession(options: { From 96adf6c3b1294d7f58395548c2529bbab5d3efd0 Mon Sep 17 00:00:00 2001 From: Paul Asjes Date: Fri, 27 Dec 2024 17:03:14 +0200 Subject: [PATCH 3/3] Update test --- __tests__/session.spec.ts | 54 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/__tests__/session.spec.ts b/__tests__/session.spec.ts index 14f2abb..e1ccb59 100644 --- a/__tests__/session.spec.ts +++ b/__tests__/session.spec.ts @@ -245,7 +245,7 @@ describe('session.ts', () => { expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Refresh successful. New access token ends in')); }); - it('should delete the cookie and redirect when refreshing fails', async () => { + it('should delete the cookie when refreshing fails', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); mockSession.accessToken = await generateTestToken({}, true); @@ -277,7 +277,7 @@ describe('session.ts', () => { [], ); - expect(result.status).toBe(307); + expect(result.status).toBe(200); expect(nextCookies.get('wos-session')).toBeUndefined(); expect(console.log).toHaveBeenCalledTimes(2); expect(console.log).toHaveBeenNthCalledWith( @@ -286,7 +286,7 @@ describe('session.ts', () => { ); expect(console.log).toHaveBeenNthCalledWith( 2, - 'Failed to refresh. Deleting cookie and redirecting.', + 'Failed to refresh. Deleting cookie.', new Error('Failed to refresh'), ); }); @@ -448,6 +448,54 @@ describe('session.ts', () => { expect(result.status).toBe(307); }); + it('should delete the cookie and redirect when refreshing fails', async () => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + + mockSession.accessToken = await generateTestToken({}, true); + + const nextCookies = await cookies(); + nextCookies.set( + 'wos-session', + await sealData(mockSession, { password: process.env.WORKOS_COOKIE_PASSWORD as string }), + ); + + (jwtVerify as jest.Mock).mockImplementation(() => { + throw new Error('Invalid token'); + }); + + jest + .spyOn(workos.userManagement, 'authenticateWithRefreshToken') + .mockRejectedValue(new Error('Failed to refresh')); + + const request = new NextRequest(new URL('http://example.com')); + + const result = await updateSession( + request, + true, + { + enabled: true, + unauthenticatedPaths: [], + }, + process.env.NEXT_PUBLIC_WORKOS_REDIRECT_URI as string, + [], + ); + + expect(result.status).toBe(307); + expect(nextCookies.get('wos-session')).toBeUndefined(); + expect(console.log).toHaveBeenCalledTimes(3); + expect(console.log).toHaveBeenNthCalledWith( + 1, + `Session invalid. Refreshing access token that ends in ${mockSession.accessToken.slice(-10)}`, + ); + expect(console.log).toHaveBeenNthCalledWith( + 2, + 'Failed to refresh. Deleting cookie.', + new Error('Failed to refresh'), + ); + + expect(console.log).toHaveBeenNthCalledWith(3, 'Redirecting to AuthKit to log in again.'); + }); + describe('sign up paths', () => { it('should redirect to sign up when unauthenticated user is on a sign up path', async () => { const request = new NextRequest(new URL('http://example.com/signup'));