Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only redirect after refresh failure in middleware auth mode #155

Merged
merged 5 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions __tests__/session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,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);
Expand Down Expand Up @@ -323,7 +323,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(
Expand All @@ -332,7 +332,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'),
);
});
Expand Down Expand Up @@ -494,6 +494,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'));
Expand Down
20 changes: 14 additions & 6 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,24 @@ 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);
}

// 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 redirectWithFallback(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.
if (debug) console.log('Redirecting to AuthKit to log in again.');
return redirectWithFallback(request.url);
}

// If we aren't in middleware auth mode, we return a response and let the page handle what to do next.
return NextResponse.next({
request: { headers: newRequestHeaders },
});
}

async function refreshSession(options: {
Expand Down
Loading