Skip to content

Commit 7c8601f

Browse files
authored
Add advanced use-cases to README (#53)
1 parent bd07e65 commit 7c8601f

File tree

1 file changed

+140
-85
lines changed

1 file changed

+140
-85
lines changed

README.md

Lines changed: 140 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AuthKit React Router Library
22

3-
>[!IMPORTANT]
3+
> [!IMPORTANT]
44
> This is an early-stage port of [authkit-remix](https://github.com/workos/authkit-remix) to support React Router. The features focus on framework mode (e.g. Remix), with more planned support for library mode and more features.
55
66
The AuthKit library for React Router 7+ provides convenient helpers for authentication and session management using WorkOS & AuthKit with React Router. You can find this library in action in the [react-router-authkit-example](https://github.com/workos/react-router-authkit-example) repo.
@@ -25,13 +25,13 @@ AuthKit for React Router offers a flexible configuration system that allows you
2525

2626
### 1. Environment Variables
2727

28-
The simplest way is to set environment variables in your `.env.local` file:
28+
The simplest way is to set environment variables in your `.env.local` file:
2929

30-
```bash
31-
WORKOS_CLIENT_ID="client_..." # retrieved from the WorkOS dashboard
32-
WORKOS_API_KEY="sk_test_..." # retrieved from the WorkOS dashboard
33-
WORKOS_REDIRECT_URI="http://localhost:5173/callback" # configured in the WorkOS dashboard
34-
WORKOS_COOKIE_PASSWORD="<your password>" # generate a secure password here
30+
```bash
31+
WORKOS_CLIENT_ID="client_..." # retrieved from the WorkOS dashboard
32+
WORKOS_API_KEY="sk_test_..." # retrieved from the WorkOS dashboard
33+
WORKOS_REDIRECT_URI="http://localhost:5173/callback" # configured in the WorkOS dashboard
34+
WORKOS_COOKIE_PASSWORD="<your password>" # generate a secure password here
3535
```
3636

3737
### 2. Programmatic Configuration
@@ -59,17 +59,14 @@ For non-standard environments (like Deno or Edge functions), you can provide a c
5959

6060
> [!Warning]
6161
>
62-
>While this library includes support for custom environment sources that could theoretically work in non-Node.js runtimes like Deno or Edge functions, this functionality has not been extensively tested (yet). If you're planning to use AuthKit in these environments, you may encounter unexpected issues. We welcome feedback and contributions from users who test in these environments.
62+
> While this library includes support for custom environment sources that could theoretically work in non-Node.js runtimes like Deno or Edge functions, this functionality has not been extensively tested (yet). If you're planning to use AuthKit in these environments, you may encounter unexpected issues. We welcome feedback and contributions from users who test in these environments.
6363
6464
```typescript
6565
import { configure } from '@workos-inc/authkit-react-router';
6666

67-
configure(key => Deno.env.get(key));
67+
configure((key) => Deno.env.get(key));
6868
// Or combine with explicit values
69-
configure(
70-
{ clientId: 'client_1234567890' },
71-
key => Deno.env.get(key)
72-
);
69+
configure({ clientId: 'client_1234567890' }, (key) => Deno.env.get(key));
7370
```
7471

7572
### Configuration Priority
@@ -82,25 +79,25 @@ When retrieving configuration values, AuthKit follows this priority order:
8279

8380
### Available Configuration Options
8481

85-
>[!NOTE]
82+
> [!NOTE]
8683
>
87-
>To print out the entire config, a `getFullConfig` function is provided for debugging purposes.
88-
89-
| Option | Environment Variable | Default | Required | Description |
90-
| ---- | ---- | ---- | ---- | ---- |
91-
| `clientId` | `WORKOS_CLIENT_ID` | - | Yes | Your WorkOS Client ID |
92-
| `apiKey` | `WORKOS_API_KEY` | - | Yes | Your WorkOS API Key |
93-
| `redirectUri` | `WORKOS_REDIRECT_URI` | - | Yes | The callback URL configured in WorkOS |
94-
| `cookiePassword` | `WORKOS_COOKIE_PASSWORD` | - | Yes | Password for cookie encryption (min 32 chars) |
95-
| `cookieName` | `WORKOS_COOKIE_NAME` | `wos-session` | No | Name of the session cookie |
96-
| `apiHttps` | `WORKOS_API_HTTPS` | `true` | No | Whether to use HTTPS for API calls |
97-
| `cookieMaxAge` | `WORKOS_COOKIE_MAX_AGE` | `34560000` (400 days) | No | Maximum age of cookie in seconds |
98-
| `apiHostname` | `WORKOS_API_HOSTNAME` | `api.workos.com` | No | WorkOS API hostname |
99-
| `apiPort` | `WORKOS_API_PORT` | - | No | Port to use for API calls |
100-
101-
>[!NOTE]
84+
> To print out the entire config, a `getFullConfig` function is provided for debugging purposes.
85+
86+
| Option | Environment Variable | Default | Required | Description |
87+
| ---------------- | ------------------------ | --------------------- | -------- | --------------------------------------------- |
88+
| `clientId` | `WORKOS_CLIENT_ID` | - | Yes | Your WorkOS Client ID |
89+
| `apiKey` | `WORKOS_API_KEY` | - | Yes | Your WorkOS API Key |
90+
| `redirectUri` | `WORKOS_REDIRECT_URI` | - | Yes | The callback URL configured in WorkOS |
91+
| `cookiePassword` | `WORKOS_COOKIE_PASSWORD` | - | Yes | Password for cookie encryption (min 32 chars) |
92+
| `cookieName` | `WORKOS_COOKIE_NAME` | `wos-session` | No | Name of the session cookie |
93+
| `apiHttps` | `WORKOS_API_HTTPS` | `true` | No | Whether to use HTTPS for API calls |
94+
| `cookieMaxAge` | `WORKOS_COOKIE_MAX_AGE` | `34560000` (400 days) | No | Maximum age of cookie in seconds |
95+
| `apiHostname` | `WORKOS_API_HOSTNAME` | `api.workos.com` | No | WorkOS API hostname |
96+
| `apiPort` | `WORKOS_API_PORT` | - | No | Port to use for API calls |
97+
98+
> [!NOTE]
10299
>
103-
>The `cookiePassword` must be at least 32 characters long for security reasons.
100+
> The `cookiePassword` must be at least 32 characters long for security reasons.
104101
105102
## Setup
106103

@@ -161,14 +158,7 @@ export function App() {
161158
For pages where you want to display a signed-in and signed-out view, use `authkitLoader` to retrieve the user profile from WorkOS. You can pass in additional data by providing a loader function directly to `authkitLoader`.
162159

163160
```tsx
164-
import {
165-
type ActionFunctionArgs,
166-
type LoaderFunctionArgs,
167-
data,
168-
Form,
169-
Link,
170-
useLoaderData
171-
} from 'react-router';
161+
import { type ActionFunctionArgs, type LoaderFunctionArgs, data, Form, Link, useLoaderData } from 'react-router';
172162
import { getSignInUrl, getSignUpUrl, signOut, authkitLoader } from '@workos-inc/authkit-react-router';
173163

174164
export const loader = (args: LoaderFunctionArgs) =>
@@ -245,7 +235,7 @@ export const loader = (args: LoaderFunctionArgs) =>
245235

246236
// Explicitly call the function to get the access token
247237
const accessToken = getAccessToken();
248-
238+
249239
const serviceData = await fetch('/api/path', {
250240
headers: {
251241
Authorization: `Bearer ${accessToken}`,
@@ -261,26 +251,32 @@ export const loader = (args: LoaderFunctionArgs) =>
261251
#### Security Considerations
262252

263253
By default, access tokens are not included in the data sent to React components. This helps prevent unintentional token exposure in:
254+
264255
- Browser developer tools
265-
- HTML source code
256+
- HTML source code
266257
- Client-side logs or error reporting
267258

268259
If you need to expose the access token to client-side code, you can explicitly return it from your loader:
269260

270261
```tsx
271262
export const loader = (args: LoaderFunctionArgs) =>
272-
authkitLoader(args, async ({ auth, getAccessToken }) => {
273-
const accessToken = getAccessToken();
274-
275-
return {
276-
// Only expose to client if absolutely necessary
277-
accessToken,
278-
userData: await fetchUserData(accessToken)
279-
};
280-
}, { ensureSignedIn: true });
263+
authkitLoader(
264+
args,
265+
async ({ auth, getAccessToken }) => {
266+
const accessToken = getAccessToken();
267+
268+
return {
269+
// Only expose to client if absolutely necessary
270+
accessToken,
271+
userData: await fetchUserData(accessToken),
272+
};
273+
},
274+
{ ensureSignedIn: true },
275+
);
281276
```
282277

283278
**Note:** Only expose access tokens to the client when necessary for your use case (e.g., making direct API calls from the browser). Consider alternatives like:
279+
284280
- Making API calls server-side in your loaders
285281
- Creating proxy endpoints in your application
286282
- Using separate client-specific tokens with limited scope
@@ -291,23 +287,27 @@ When using the `ensureSignedIn` option, you can be confident that `getAccessToke
291287

292288
```tsx
293289
export const loader = (args: LoaderFunctionArgs) =>
294-
authkitLoader(args, async ({ auth, getAccessToken }) => {
295-
// With ensureSignedIn: true, the user is guaranteed to be authenticated
296-
const accessToken = getAccessToken();
297-
298-
// Use the token for your API calls
299-
const data = await fetchProtectedData(accessToken);
300-
301-
return { data };
302-
}, { ensureSignedIn: true });
290+
authkitLoader(
291+
args,
292+
async ({ auth, getAccessToken }) => {
293+
// With ensureSignedIn: true, the user is guaranteed to be authenticated
294+
const accessToken = getAccessToken();
295+
296+
// Use the token for your API calls
297+
const data = await fetchProtectedData(accessToken);
298+
299+
return { data };
300+
},
301+
{ ensureSignedIn: true },
302+
);
303303
```
304304

305305
### Using withAuth for low-level access
306306

307307
For advanced use cases, the `withAuth` function provides direct access to authentication data, including the access token. Unlike `authkitLoader`, this function:
308308

309309
- Does not handle automatic token refresh
310-
- Does not manage cookies or session updates
310+
- Does not manage cookies or session updates
311311
- Returns the access token directly as a property
312312
- Requires manual redirect handling for unauthenticated users
313313

@@ -317,20 +317,20 @@ import { redirect, type LoaderFunctionArgs } from 'react-router';
317317

318318
export const loader = async (args: LoaderFunctionArgs) => {
319319
const auth = await withAuth(args);
320-
320+
321321
if (!auth.user) {
322322
// Manual redirect - withAuth doesn't handle this automatically
323323
throw redirect('/sign-in');
324324
}
325-
325+
326326
// Access token is directly available as a property
327327
const { accessToken, user, sessionId } = auth;
328-
328+
329329
// Use the token for server-side operations
330330
const apiData = await fetch('https://api.example.com/data', {
331-
headers: { Authorization: `Bearer ${accessToken}` }
331+
headers: { Authorization: `Bearer ${accessToken}` },
332332
});
333-
333+
334334
// Be careful what you return - accessToken will be exposed if included
335335
return {
336336
user,
@@ -346,6 +346,57 @@ export const loader = async (args: LoaderFunctionArgs) => {
346346
- Use `withAuth` when you need more control or are building custom authentication flows
347347
- `withAuth` is useful for API routes or middleware where you don't need the full loader functionality
348348

349+
### Advanced: Direct access to the WorkOS client
350+
351+
For advanced use cases or functionality not covered by the helper methods, you can access the underlying WorkOS client directly:
352+
353+
```ts
354+
import { getWorkOS } from '@workos-inc/authkit-react-router';
355+
356+
// Get the configured WorkOS client instance
357+
const workos = getWorkOS();
358+
359+
// Use any WorkOS SDK method
360+
const organizations = await workos.organizations.listOrganizations({
361+
limit: 10,
362+
});
363+
```
364+
365+
### Advanced: Custom authentication flows
366+
367+
While the standard authentication flow handles session management automatically, some use cases require manually creating and storing a session. This is useful for custom authentication flows like email verification or token exchange.
368+
369+
For these scenarios, you can use the `saveSession` function:
370+
371+
```ts
372+
import { redirect } from 'react-router';
373+
import { getWorkOS, saveSession } from '@workos-inc/authkit-react-router';
374+
375+
// Example: Email verification flow
376+
async function handleEmailVerification(request: Request) {
377+
const { code } = await request.json();
378+
379+
// Authenticate with the WorkOS API directly
380+
const authResponse = await getWorkOS().userManagement.authenticateWithEmailVerification({
381+
clientId: process.env.WORKOS_CLIENT_ID,
382+
code,
383+
});
384+
385+
// Save the session data to a cookie
386+
await saveSession(
387+
{
388+
accessToken: authResponse.accessToken,
389+
refreshToken: authResponse.refreshToken,
390+
user: authResponse.user,
391+
impersonator: authResponse.impersonator,
392+
},
393+
request,
394+
);
395+
396+
return redirect('/dashboard');
397+
}
398+
```
399+
349400
### Debugging
350401

351402
To enable debug logs, pass in the debug flag when using `authkitLoader`.
@@ -392,32 +443,33 @@ By default, AuthKit for React Router uses cookie-based session storage with thes
392443
You can provide your own session storage implementation to both `authkitLoader` and `authLoader`:
393444

394445
```typescript
395-
import { createMemorySessionStorage } from "@react-router/node";
396-
import { authkitLoader, authLoader } from "@workos-inc/authkit-react-router";
446+
import { createMemorySessionStorage } from '@react-router/node';
447+
import { authkitLoader, authLoader } from '@workos-inc/authkit-react-router';
397448

398449
// Create memory-based session storage
399450
const memoryStorage = createMemorySessionStorage({
400451
cookie: {
401-
name: "auth-session",
402-
secrets: ["test-secret"],
403-
sameSite: "lax",
404-
path: "/",
452+
name: 'auth-session',
453+
secrets: ['test-secret'],
454+
sameSite: 'lax',
455+
path: '/',
405456
httpOnly: true,
406457
secure: false, // Use false for testing
407-
maxAge: 60 * 60 * 24 // 1 day
408-
}
458+
maxAge: 60 * 60 * 24, // 1 day
459+
},
409460
});
410461

411462
// In your root loader
412-
export const loader = (args) => authkitLoader(args, {
413-
storage: memoryStorage,
414-
cookie: { name: "auth-session" }
415-
});
463+
export const loader = (args) =>
464+
authkitLoader(args, {
465+
storage: memoryStorage,
466+
cookie: { name: 'auth-session' },
467+
});
416468

417469
// In your callback route
418470
export const loader = authLoader({
419471
storage: memoryStorage,
420-
cookie: { name: "auth-session" }
472+
cookie: { name: 'auth-session' },
421473
});
422474
```
423475

@@ -426,19 +478,22 @@ For code reuse and consistency, consider using a shared function:
426478
```typescript
427479
// app/lib/session.ts
428480
export function getAuthStorage() {
429-
const storage = createCookieSessionStorage({/* config */});
430-
return { storage, cookie: { name: "my-custom-session" } };
481+
const storage = createCookieSessionStorage({
482+
/* config */
483+
});
484+
return { storage, cookie: { name: 'my-custom-session' } };
431485
}
432486

433487
// Then in your routes
434-
import { getAuthStorage } from "~/lib/session";
435-
export const loader = (args) => authkitLoader(args, {
436-
...getAuthStorage(),
437-
// Other options...
438-
});
488+
import { getAuthStorage } from '~/lib/session';
489+
export const loader = (args) =>
490+
authkitLoader(args, {
491+
...getAuthStorage(),
492+
// Other options...
493+
});
439494
```
440495

441496
> [!NOTE]
442-
>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.
497+
> 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.
443498
444499
AuthKit works with any session storage that implements React Router's `SessionStorage` interface, including Redis-based or database-backed implementations.

0 commit comments

Comments
 (0)