Skip to content

Commit f627a1d

Browse files
authored
allow configuring session storage on callback route (#22)
This PR addresses issue #63 where users deploying to serverless environments (like AWS Lambda) experience errors in the callback route during cold starts. The error occurs because the session storage configuration happens in the root loader, but during cold starts the callback route might be hit first, resulting in: ``` Error: SessionStorage was never configured. Did you forget to call configureSessionStorage in your root loader? ``` ## Changes - Updated the `authLoader` function to accept the same `storage` and `cookie` configuration options that `authkitLoader` already supports - Modified `HandleAuthOptions` type to support these new configuration options - Changed the implementation to use `configureSessionStorage` directly in the callback route ## How It Works When a cold start happens in a serverless environment, the callback route can now configure session storage itself through the same interface already available in `authkitLoader`. This allows developers to provide consistent session storage configuration to both routes, ensuring the application works correctly regardless of which route is hit first. ## Usage For applications deployed to serverless environments, you can now use this pattern: ```typescript // Create a shared configuration function function getAuthStorage() { return { storage: createCookieSessionStorage({...}), cookie: { name: "my-session" } }; } // In your root loader export const loader = (args) => authkitLoader(args, { ...getAuthStorage(), // Other options... }); // In your callback route export const loader = authLoader({ ...getAuthStorage(), // Other options... }); ``` This ensures consistent session configuration across routes and prevents cold start errors. ## README changes This PR also adds a section describing sessoion storage configuration to the README, and notes the important case to configure in both laoders (when custom session storage is used). Fixes #63.
1 parent 3712085 commit f627a1d

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,75 @@ export const loader = (args: LoaderFunctionArgs) =>
280280
{ debug: true },
281281
);
282282
```
283+
284+
## Customizing Session Storage
285+
286+
By default, AuthKit for React Router uses cookie-based session storage with these settings:
287+
288+
```typescript
289+
{
290+
name: "wos-session", // Default or WORKOS_COOKIE_NAME if set
291+
path: "/",
292+
httpOnly: true,
293+
secure: true, // When redirect URI uses HTTPS
294+
sameSite: "lax",
295+
maxAge: 34560000, // 400 days (configurable via WORKOS_COOKIE_MAX_AGE)
296+
secrets: [/* your cookie password, configurable via WORKOS_COOKIE_PASSWORD */],
297+
}
298+
```
299+
300+
### Custom Session Storage
301+
302+
You can provide your own session storage implementation to both `authkitLoader` and `authLoader`:
303+
304+
```typescript
305+
import { createMemorySessionStorage } from "@react-router/node";
306+
import { authkitLoader, authLoader } from "@workos-inc/authkit-react-router";
307+
308+
// Create memory-based session storage
309+
const memoryStorage = createMemorySessionStorage({
310+
cookie: {
311+
name: "auth-session",
312+
secrets: ["test-secret"],
313+
sameSite: "lax",
314+
path: "/",
315+
httpOnly: true,
316+
secure: false, // Use false for testing
317+
maxAge: 60 * 60 * 24 // 1 day
318+
}
319+
});
320+
321+
// In your root loader
322+
export const loader = (args) => authkitLoader(args, {
323+
storage: memoryStorage,
324+
cookie: { name: "auth-session" }
325+
});
326+
327+
// In your callback route
328+
export const loader = authLoader({
329+
storage: memoryStorage,
330+
cookie: { name: "auth-session" }
331+
});
332+
```
333+
334+
For code reuse and consistency, consider using a shared function:
335+
336+
```typescript
337+
// app/lib/session.ts
338+
export function getAuthStorage() {
339+
const storage = createCookieSessionStorage({/* config */});
340+
return { storage, cookie: { name: "my-custom-session" } };
341+
}
342+
343+
// Then in your routes
344+
import { getAuthStorage } from "~/lib/session";
345+
export const loader = (args) => authkitLoader(args, {
346+
...getAuthStorage(),
347+
// Other options...
348+
});
349+
```
350+
351+
> [!NOTE]
352+
>When deploying to serverless environments like AWS Lambda, ensure you pass the same storage configuration to both your main routes and the callback route to handle cold starts properly.
353+
354+
AuthKit works with any session storage that implements React Router's `SessionStorage` interface, including Redis-based or database-backed implementations.

src/authkit-callback-route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { LoaderFunctionArgs, data, redirect } from 'react-router';
22
import { getConfig } from './config.js';
33
import { HandleAuthOptions } from './interfaces.js';
44
import { encryptSession } from './session.js';
5-
import { getSessionStorage } from './sessionStorage.js';
5+
import { configureSessionStorage } from './sessionStorage.js';
66
import { getWorkOS } from './workos.js';
77

88
export function authLoader(options: HandleAuthOptions = {}) {
99
return async function loader({ request }: LoaderFunctionArgs) {
10-
const { getSession, commitSession, cookieName } = await getSessionStorage();
11-
const { returnPathname: returnPathnameOption = '/', onSuccess } = options;
10+
const { storage, cookie, returnPathname: returnPathnameOption = '/', onSuccess } = options;
11+
const cookieName = cookie?.name ?? getConfig('cookieName');
12+
const { getSession, commitSession } = await configureSessionStorage({ storage, cookieName });
1213

1314
const url = new URL(request.url);
1415

src/interfaces.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ import type { OauthTokens, User } from '@workos-inc/node';
33

44
export type DataWithResponseInit<T> = ReturnType<typeof data<T>>;
55

6-
export interface HandleAuthOptions {
6+
export type HandleAuthOptions = {
77
returnPathname?: string;
88
onSuccess?: (data: AuthLoaderSuccessData) => void | Promise<void>;
9-
}
9+
} & (
10+
| {
11+
storage?: never;
12+
cookie?: SessionIdStorageStrategy['cookie'];
13+
}
14+
| {
15+
storage: SessionStorage;
16+
cookie: SessionIdStorageStrategy['cookie'];
17+
}
18+
);
1019

1120
export interface AuthLoaderSuccessData {
1221
accessToken: string;

0 commit comments

Comments
 (0)