Skip to content

Commit e49600d

Browse files
authored
Fix: Preserve return pathname on session refresh failure (#31)
1 parent 540ba02 commit e49600d

File tree

2 files changed

+24
-4
lines changed

2 files changed

+24
-4
lines changed

src/session.spec.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@ import { authkitLoader, encryptSession, terminateSession, refreshSession } from
1111
import { assertIsResponse } from './test-utils/test-helpers.js';
1212
import { getWorkOS } from './workos.js';
1313
import { getConfig } from './config.js';
14+
import { getAuthorizationUrl } from './get-authorization-url.js';
1415

1516
jest.mock('./sessionStorage.js', () => ({
1617
configureSessionStorage: jest.fn(),
1718
getSessionStorage: jest.fn(),
1819
}));
1920

21+
jest.mock('./get-authorization-url.js', () => ({
22+
getAuthorizationUrl: jest.fn(),
23+
}));
24+
2025
// Mock dependencies
2126
const fakeWorkosInstance = {
2227
userManagement: {
@@ -39,6 +44,7 @@ const authenticateWithRefreshToken = jest.mocked(workos.userManagement.authentic
3944
const getSessionStorage = jest.mocked(getSessionStorageMock);
4045
const configureSessionStorage = jest.mocked(configureSessionStorageMock);
4146
const jwtVerify = jest.mocked(jose.jwtVerify);
47+
const getAuthorizationUrlMock = jest.mocked(getAuthorizationUrl);
4248

4349
function getHeaderValue(headers: HeadersInit | undefined, name: string): string | null {
4450
if (!headers) {
@@ -113,6 +119,10 @@ describe('session', () => {
113119
destroySession,
114120
commitSession,
115121
});
122+
123+
// Reset getAuthorizationUrl mock
124+
getAuthorizationUrlMock.mockReset();
125+
getAuthorizationUrlMock.mockResolvedValue('https://auth.workos.com/oauth/authorize');
116126
});
117127

118128
describe('encryptSession', () => {
@@ -594,17 +604,26 @@ describe('session', () => {
594604
expect(getHeaderValue(init?.headers, 'Set-Cookie')).toBe('new-session-cookie');
595605
});
596606

597-
it('should redirect to root when refresh fails', async () => {
607+
it('should redirect to authorization URL preserving returnPathname when refresh fails', async () => {
598608
authenticateWithRefreshToken.mockRejectedValue(new Error('Refresh token invalid'));
599609

610+
// Setup the mock to return a URL with state parameter
611+
getAuthorizationUrlMock.mockResolvedValue('https://auth.workos.com/oauth/authorize?state=abc123');
612+
600613
try {
601-
await authkitLoader(createLoaderArgs(createMockRequest()));
614+
const mockRequest = createMockRequest('test-cookie', 'https://app.example.com/dashboard/settings');
615+
await authkitLoader(createLoaderArgs(mockRequest));
602616
fail('Expected redirect response to be thrown');
603617
} catch (response: unknown) {
604618
assertIsResponse(response);
605619
expect(response.status).toBe(302);
606-
expect(response.headers.get('Location')).toBe('/');
620+
expect(response.headers.get('Location')).toBe('https://auth.workos.com/oauth/authorize?state=abc123');
607621
expect(response.headers.get('Set-Cookie')).toBe('destroyed-session-cookie');
622+
623+
// Verify getAuthorizationUrl was called with the correct returnPathname
624+
expect(getAuthorizationUrlMock).toHaveBeenCalledWith({
625+
returnPathname: '/dashboard/settings',
626+
});
608627
}
609628
});
610629

src/session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ export async function authkitLoader<Data = unknown>(
393393
}
394394
}
395395

396-
throw redirect('/', {
396+
const returnPathname = getReturnPathname(request.url);
397+
throw redirect(await getAuthorizationUrl({ returnPathname }), {
397398
headers: {
398399
'Set-Cookie': await destroySession(cookieSession),
399400
},

0 commit comments

Comments
 (0)