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'));