@workos-inc/authkit-nextjs 3.0.0-beta.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +305 -102
- package/dist/esm/actions.js +35 -5
- package/dist/esm/actions.js.map +1 -1
- package/dist/esm/auth.js +71 -21
- package/dist/esm/auth.js.map +1 -1
- package/dist/esm/authkit-callback-route.js +90 -92
- package/dist/esm/authkit-callback-route.js.map +1 -1
- package/dist/esm/components/authkit-provider.js +36 -15
- package/dist/esm/components/authkit-provider.js.map +1 -1
- package/dist/esm/components/impersonation.js +17 -15
- package/dist/esm/components/impersonation.js.map +1 -1
- package/dist/esm/components/min-max-button.js +1 -1
- package/dist/esm/components/min-max-button.js.map +1 -1
- package/dist/esm/components/tokenStore.js +28 -19
- package/dist/esm/components/tokenStore.js.map +1 -1
- package/dist/esm/components/useAccessToken.js +1 -1
- package/dist/esm/components/useAccessToken.js.map +1 -1
- package/dist/esm/components/useTokenClaims.js +1 -1
- package/dist/esm/components/useTokenClaims.js.map +1 -1
- package/dist/esm/cookie.js +20 -5
- package/dist/esm/cookie.js.map +1 -1
- package/dist/esm/env-variables.js +6 -6
- package/dist/esm/env-variables.js.map +1 -1
- package/dist/esm/errors.js +36 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/get-authorization-url.js +51 -12
- package/dist/esm/get-authorization-url.js.map +1 -1
- package/dist/esm/index.js +5 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interfaces.js +7 -1
- package/dist/esm/interfaces.js.map +1 -1
- package/dist/esm/middleware-helpers.js +102 -0
- package/dist/esm/middleware-helpers.js.map +1 -0
- package/dist/esm/middleware.js +3 -1
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/pkce.js +52 -0
- package/dist/esm/pkce.js.map +1 -0
- package/dist/esm/session.js +82 -35
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/test-helpers.js +1 -1
- package/dist/esm/test-helpers.js.map +1 -1
- package/dist/esm/types/actions.d.ts +34 -5
- package/dist/esm/types/auth.d.ts +7 -15
- package/dist/esm/types/components/authkit-provider.d.ts +6 -2
- package/dist/esm/types/components/impersonation.d.ts +2 -1
- package/dist/esm/types/cookie.d.ts +9 -0
- package/dist/esm/types/env-variables.d.ts +2 -1
- package/dist/esm/types/errors.d.ts +15 -0
- package/dist/esm/types/get-authorization-url.d.ts +2 -2
- package/dist/esm/types/index.d.ts +5 -2
- package/dist/esm/types/interfaces.d.ts +12 -0
- package/dist/esm/types/jwt.d.ts +9 -9
- package/dist/esm/types/middleware-helpers.d.ts +27 -0
- package/dist/esm/types/middleware.d.ts +3 -1
- package/dist/esm/types/pkce.d.ts +17 -0
- package/dist/esm/types/session.d.ts +1 -1
- package/dist/esm/types/utils.d.ts +5 -0
- package/dist/esm/types/validate-api-key.d.ts +1 -0
- package/dist/esm/types/workos.d.ts +1 -1
- package/dist/esm/utils.js +10 -2
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/validate-api-key.js +16 -0
- package/dist/esm/validate-api-key.js.map +1 -0
- package/dist/esm/workos.js +1 -1
- package/package.json +33 -34
- package/src/actions.spec.ts +91 -18
- package/src/actions.ts +44 -6
- package/src/auth.spec.ts +79 -29
- package/src/auth.ts +74 -42
- package/src/authkit-callback-route.spec.ts +372 -58
- package/src/authkit-callback-route.ts +121 -103
- package/src/components/authkit-provider.spec.tsx +264 -70
- package/src/components/authkit-provider.tsx +40 -15
- package/src/components/button.spec.tsx +4 -6
- package/src/components/impersonation.spec.tsx +152 -35
- package/src/components/impersonation.tsx +37 -30
- package/src/components/min-max-button.spec.tsx +2 -1
- package/src/components/tokenStore.spec.ts +59 -44
- package/src/components/tokenStore.ts +11 -3
- package/src/components/useAccessToken.spec.tsx +82 -83
- package/src/components/useTokenClaims.spec.tsx +23 -22
- package/src/cookie.spec.ts +63 -9
- package/src/cookie.ts +35 -0
- package/src/env-variables.ts +2 -0
- package/src/errors.spec.ts +108 -0
- package/src/errors.ts +46 -0
- package/src/get-authorization-url.spec.ts +170 -15
- package/src/get-authorization-url.ts +69 -23
- package/src/index.ts +20 -2
- package/src/interfaces.ts +15 -0
- package/src/jwt.ts +9 -9
- package/src/middleware-helpers.spec.ts +238 -0
- package/src/middleware-helpers.ts +134 -0
- package/src/middleware.spec.ts +25 -0
- package/src/middleware.ts +4 -1
- package/src/pkce.spec.ts +146 -0
- package/src/pkce.ts +59 -0
- package/src/session.spec.ts +87 -89
- package/src/session.ts +104 -27
- package/src/test-helpers.ts +1 -1
- package/src/utils.spec.ts +14 -31
- package/src/utils.ts +9 -0
- package/src/validate-api-key.spec.ts +111 -0
- package/src/validate-api-key.ts +19 -0
- package/src/workos.spec.ts +2 -2
- package/src/workos.ts +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The AuthKit library for Next.js provides convenient helpers for authentication a
|
|
|
9
9
|
Install the package with:
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
|
|
12
|
+
pnpm i @workos-inc/authkit-nextjs
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
or
|
|
@@ -100,8 +100,9 @@ export const GET = handleAuth({
|
|
|
100
100
|
await saveAuthMethod(user.id, authenticationMethod);
|
|
101
101
|
}
|
|
102
102
|
// Access custom state data passed through the auth flow
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
const customData = state ? JSON.parse(state) : null;
|
|
104
|
+
if (customData?.teamId) {
|
|
105
|
+
await addUserToTeam(user.id, customData.teamId);
|
|
105
106
|
}
|
|
106
107
|
},
|
|
107
108
|
});
|
|
@@ -128,39 +129,81 @@ export const GET = handleAuth({
|
|
|
128
129
|
|
|
129
130
|
The `onSuccess` callback receives the following data:
|
|
130
131
|
|
|
131
|
-
| Property | Type
|
|
132
|
-
| ---------------------- |
|
|
133
|
-
| `user` | `User`
|
|
134
|
-
| `accessToken` | `string`
|
|
135
|
-
| `refreshToken` | `string`
|
|
136
|
-
| `impersonator` | `Impersonator \| undefined`
|
|
137
|
-
| `oauthTokens` | `OauthTokens \| undefined`
|
|
138
|
-
| `authenticationMethod` | `string \| undefined`
|
|
139
|
-
| `organizationId` | `string \| undefined`
|
|
140
|
-
| `state` | `
|
|
132
|
+
| Property | Type | Description |
|
|
133
|
+
| ---------------------- | --------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `user` | `User` | The authenticated user object |
|
|
135
|
+
| `accessToken` | `string` | JWT access token |
|
|
136
|
+
| `refreshToken` | `string` | Refresh token for session renewal |
|
|
137
|
+
| `impersonator` | `Impersonator \| undefined` | Present if user is being impersonated |
|
|
138
|
+
| `oauthTokens` | `OauthTokens \| undefined` | OAuth tokens from upstream provider |
|
|
139
|
+
| `authenticationMethod` | `string \| undefined` | How the user authenticated (e.g., 'password', 'google-oauth'). Only available during initial login |
|
|
140
|
+
| `organizationId` | `string \| undefined` | Organization context of authentication |
|
|
141
|
+
| `state` | `string \| undefined` | Custom state string passed through the authentication flow (parse with JSON.parse if needed) |
|
|
141
142
|
|
|
142
143
|
**Note**: `authenticationMethod` is only provided during the initial authentication callback. It will not be available in subsequent requests or session refreshes.
|
|
143
144
|
|
|
144
|
-
###
|
|
145
|
+
### Sign-in endpoint
|
|
145
146
|
|
|
146
|
-
This
|
|
147
|
+
Create a route that initiates the AuthKit sign-in flow. This route is used as the **[Sign-in endpoint](https://workos.com/docs/authkit/nextjs/2-configure-your-project/configure-a-redirect-uri#sign-in-endpoint)** (also known as `initiate_login_uri`) in your WorkOS dashboard settings.
|
|
147
148
|
|
|
148
149
|
```ts
|
|
150
|
+
// app/sign-in/route.ts (or app/login/route.ts)
|
|
151
|
+
import { getSignInUrl } from '@workos-inc/authkit-nextjs';
|
|
152
|
+
import { redirect } from 'next/navigation';
|
|
153
|
+
|
|
154
|
+
export const GET = async () => {
|
|
155
|
+
const signInUrl = await getSignInUrl();
|
|
156
|
+
return redirect(signInUrl);
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
In the [WorkOS dashboard](https://dashboard.workos.com), go to **Redirects** and set the **Sign-in endpoint** to match this route (e.g., `http://localhost:3000/sign-in`).
|
|
161
|
+
|
|
162
|
+
> [!IMPORTANT]
|
|
163
|
+
> The sign-in endpoint is required for features like [impersonation](https://workos.com/docs/user-management/impersonation) to work correctly. Without it, WorkOS-initiated flows (such as impersonating a user from the dashboard) will fail because they cannot complete the PKCE/CSRF verification that this library enforces on every callback.
|
|
164
|
+
|
|
165
|
+
### Proxy / Middleware
|
|
166
|
+
|
|
167
|
+
This library relies on Next.js proxy (called "middleware" in Next.js ≤15) to provide session management for routes.
|
|
168
|
+
|
|
169
|
+
**For Next.js 16+:** Create a `proxy.ts` file in the root of your project.
|
|
170
|
+
**For Next.js ≤15:** Create a `middleware.ts` file in the root of your project.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// proxy.ts (Next.js 16+)
|
|
174
|
+
import { authkitProxy } from '@workos-inc/authkit-nextjs';
|
|
175
|
+
|
|
176
|
+
export default authkitProxy();
|
|
177
|
+
|
|
178
|
+
// Match against pages that require auth
|
|
179
|
+
export const config = { matcher: ['/', '/admin'] };
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// middleware.ts (Next.js ≤15)
|
|
149
184
|
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
|
|
150
185
|
|
|
151
186
|
export default authkitMiddleware();
|
|
152
187
|
|
|
153
188
|
// Match against pages that require auth
|
|
154
|
-
// Leave this out if you want auth on every resource (including images, css etc.)
|
|
155
189
|
export const config = { matcher: ['/', '/admin'] };
|
|
156
190
|
```
|
|
157
191
|
|
|
158
|
-
|
|
192
|
+
> [!WARNING]
|
|
193
|
+
> Using a catch-all matcher pattern can intercept static assets (CSS, images, fonts), causing styles to break—particularly with Tailwind CSS v4. If you need a broad matcher, exclude Next.js static paths:
|
|
194
|
+
>
|
|
195
|
+
> ```ts
|
|
196
|
+
> export const config = {
|
|
197
|
+
> matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
198
|
+
> };
|
|
199
|
+
> ```
|
|
200
|
+
|
|
201
|
+
The proxy/middleware can be configured with several options.
|
|
159
202
|
|
|
160
203
|
| Option | Default | Description |
|
|
161
204
|
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
162
205
|
| `redirectUri` | `undefined` | Used in cases where you need your redirect URI to be set dynamically (e.g. Vercel preview deployments) |
|
|
163
|
-
| `middlewareAuth` | `undefined` | Used to configure middleware auth options. See [middleware auth](#middleware-auth) for more details.
|
|
206
|
+
| `middlewareAuth` | `undefined` | Used to configure proxy/middleware auth options. See [middleware auth](#middleware-auth) for more details. |
|
|
164
207
|
| `debug` | `false` | Enables debug logs. |
|
|
165
208
|
| `signUpPaths` | `[]` | Used to specify paths that should use the 'sign-up' screen hint when redirecting to AuthKit. |
|
|
166
209
|
| `eagerAuth` | `false` | Enables synchronous access token availability for third-party services. See [eager auth](#eager-auth) for more details. |
|
|
@@ -177,12 +220,123 @@ export default authkitMiddleware({
|
|
|
177
220
|
});
|
|
178
221
|
|
|
179
222
|
// Match against pages that require auth
|
|
180
|
-
// Leave this out if you want auth on every resource (including images, css etc.)
|
|
181
223
|
export const config = { matcher: ['/', '/admin'] };
|
|
182
224
|
```
|
|
183
225
|
|
|
184
226
|
Custom redirect URIs will be used over a redirect URI configured in the environment variables.
|
|
185
227
|
|
|
228
|
+
#### Composable proxy/middleware
|
|
229
|
+
|
|
230
|
+
If you need to combine AuthKit with other proxy logic (rate limiting, redirects, etc.), use the `authkit()` function with `handleAuthkitHeaders()` helper:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// proxy.ts (Next.js 16+) or middleware.ts (Next.js ≤15)
|
|
234
|
+
import { NextRequest } from 'next/server';
|
|
235
|
+
import { authkit, handleAuthkitHeaders } from '@workos-inc/authkit-nextjs';
|
|
236
|
+
|
|
237
|
+
export default async function proxy(request: NextRequest) {
|
|
238
|
+
// For Next.js ≤15, use: export default async function middleware(request: NextRequest) {
|
|
239
|
+
// Get session, headers, and the WorkOS authorization URL for sign-in redirects
|
|
240
|
+
const { session, headers, authorizationUrl } = await authkit(request);
|
|
241
|
+
|
|
242
|
+
const { pathname } = request.nextUrl;
|
|
243
|
+
|
|
244
|
+
// Redirect unauthenticated users on protected routes
|
|
245
|
+
if (pathname.startsWith('/app') && !session.user && authorizationUrl) {
|
|
246
|
+
return handleAuthkitHeaders(request, headers, { redirect: authorizationUrl });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Custom redirects (relative URLs supported)
|
|
250
|
+
if (pathname === '/old-path') {
|
|
251
|
+
return handleAuthkitHeaders(request, headers, { redirect: '/new-path' });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Continue request with properly merged headers
|
|
255
|
+
return handleAuthkitHeaders(request, headers);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const config = { matcher: ['/', '/app/:path*'] };
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
> [!IMPORTANT]
|
|
262
|
+
> Always use `handleAuthkitHeaders()` when returning a response. This helper ensures:
|
|
263
|
+
>
|
|
264
|
+
> - AuthKit headers are properly passed to your pages (so `withAuth()` works)
|
|
265
|
+
> - Internal headers (session data, URLs) are never leaked to the browser
|
|
266
|
+
> - Only safe response headers (`Set-Cookie`, `Cache-Control`, `Vary`) are forwarded
|
|
267
|
+
> - `Cache-Control: no-store` is automatically set when cookies are present
|
|
268
|
+
> - `Vary` headers are properly merged when multiple values exist
|
|
269
|
+
> - Relative redirect URLs are automatically normalized to absolute URLs
|
|
270
|
+
> - POST/PUT redirects use 303 status to prevent form resubmission
|
|
271
|
+
|
|
272
|
+
> [!NOTE]
|
|
273
|
+
> The `redirect` option should only be used with trusted values (e.g., `authorizationUrl` from `authkit()` or hardcoded paths). Never pass user-controlled input directly to `redirect` without validation, as this could enable open redirect attacks.
|
|
274
|
+
|
|
275
|
+
##### Redirect options
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
handleAuthkitHeaders(request, headers, {
|
|
279
|
+
redirect: '/login', // URL to redirect to (string or URL object)
|
|
280
|
+
redirectStatus: 307, // 302 | 303 | 307 | 308 (default: 307 for GET, 303 for POST)
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
##### Advanced: Composing with rewrites
|
|
285
|
+
|
|
286
|
+
For advanced use cases like rewrites, use the lower-level `partitionAuthkitHeaders()` and `applyResponseHeaders()`:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
// proxy.ts (Next.js 16+) or middleware.ts (Next.js ≤15)
|
|
290
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
291
|
+
import { authkit, partitionAuthkitHeaders, applyResponseHeaders } from '@workos-inc/authkit-nextjs';
|
|
292
|
+
|
|
293
|
+
export default async function proxy(request: NextRequest) {
|
|
294
|
+
// For Next.js ≤15, use: export default async function middleware(request: NextRequest) {
|
|
295
|
+
const { headers } = await authkit(request);
|
|
296
|
+
const { requestHeaders, responseHeaders } = partitionAuthkitHeaders(request, headers);
|
|
297
|
+
|
|
298
|
+
// Create your own response (rewrite, etc.)
|
|
299
|
+
const response = NextResponse.rewrite(new URL('/app/dashboard', request.url), {
|
|
300
|
+
request: { headers: requestHeaders },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Apply AuthKit response headers (Set-Cookie, etc.)
|
|
304
|
+
applyResponseHeaders(response, responseHeaders);
|
|
305
|
+
|
|
306
|
+
return response;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
##### Internal headers reference
|
|
311
|
+
|
|
312
|
+
AuthKit uses internal headers to pass data between proxy/middleware and server components. These are automatically handled by the helpers above, but understanding them helps with debugging.
|
|
313
|
+
|
|
314
|
+
**Request headers** (passed to server components, never sent to browser):
|
|
315
|
+
|
|
316
|
+
| Header | Purpose |
|
|
317
|
+
| --------------------- | ------------------------------------------------------------------------------------------ |
|
|
318
|
+
| `x-workos-middleware` | Flag indicating AuthKit proxy/middleware is active. Required for `withAuth()` to function. |
|
|
319
|
+
| `x-workos-session` | Encrypted session data. Contains user info, access token, and refresh token. |
|
|
320
|
+
| `x-url` | Current request URL. Used for redirect-after-login and generating sign-in URLs. |
|
|
321
|
+
| `x-redirect-uri` | OAuth callback URI. Used by `getAuthorizationUrl()` for the OAuth flow. |
|
|
322
|
+
| `x-sign-up-paths` | Paths configured to trigger sign-up instead of sign-in flow. |
|
|
323
|
+
|
|
324
|
+
> **Security:** These headers contain sensitive session data. The `handleAuthkitHeaders()` helper ensures they're forwarded to your pages (so `withAuth()` works) but never leaked to the browser. Client-injected `x-workos-*` headers are stripped and replaced with trusted values.
|
|
325
|
+
|
|
326
|
+
**Response headers** (safe to send to browser):
|
|
327
|
+
|
|
328
|
+
| Header | Purpose |
|
|
329
|
+
| -------------------- | -------------------------------------------------------------------------------------- |
|
|
330
|
+
| `Set-Cookie` | Session cookies (e.g., `wos-session`). Multiple cookies are properly appended. |
|
|
331
|
+
| `Cache-Control` | Caching directives. Auto-set to `no-store` when cookies are present. |
|
|
332
|
+
| `Vary` | Cache variation keys. Values are deduplicated when merging. |
|
|
333
|
+
| `WWW-Authenticate` | Authentication challenge for 401 responses (API auth flows). |
|
|
334
|
+
| `Proxy-Authenticate` | Authentication challenge for proxy auth. |
|
|
335
|
+
| `Link` | Pagination, preload hints, etc. |
|
|
336
|
+
| `x-middleware-cache` | Next.js proxy/middleware result caching. Set to `no-cache` to prevent stale responses. |
|
|
337
|
+
|
|
338
|
+
Only these allowlisted headers are forwarded to the browser. Any other headers from `authkit()` (including future `x-workos-*` headers) are filtered out for security.
|
|
339
|
+
|
|
186
340
|
## Usage
|
|
187
341
|
|
|
188
342
|
### Wrap your app in `AuthKitProvider`
|
|
@@ -203,6 +357,31 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
203
357
|
}
|
|
204
358
|
```
|
|
205
359
|
|
|
360
|
+
#### Optimizing with Server-Side Auth Data
|
|
361
|
+
|
|
362
|
+
To avoid a server action call on mount, you can pass the initial auth data from the server to the `AuthKitProvider`.
|
|
363
|
+
|
|
364
|
+
```jsx
|
|
365
|
+
import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
|
|
366
|
+
import { withAuth } from '@workos-inc/authkit-nextjs';
|
|
367
|
+
|
|
368
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
369
|
+
// Fetch auth data on the server
|
|
370
|
+
const auth = await withAuth();
|
|
371
|
+
|
|
372
|
+
// Remove the accessToken from the auth object as it is not needed on the client side
|
|
373
|
+
const { accessToken, ...initialAuth } = auth;
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<html lang="en">
|
|
377
|
+
<body>
|
|
378
|
+
<AuthKitProvider initialAuth={initialAuth}>{children}</AuthKitProvider>
|
|
379
|
+
</body>
|
|
380
|
+
</html>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
206
385
|
### Get the current user in a server component
|
|
207
386
|
|
|
208
387
|
For pages where you want to display a signed-in and signed-out view, use `withAuth` to retrieve the user session from WorkOS.
|
|
@@ -224,10 +403,10 @@ export default async function HomePage() {
|
|
|
224
403
|
|
|
225
404
|
// You can also pass custom state data through the auth flow
|
|
226
405
|
const signInUrlWithState = await getSignInUrl({
|
|
227
|
-
state: {
|
|
406
|
+
state: JSON.stringify({
|
|
228
407
|
teamId: 'team_123',
|
|
229
408
|
referrer: 'homepage',
|
|
230
|
-
},
|
|
409
|
+
}),
|
|
231
410
|
});
|
|
232
411
|
|
|
233
412
|
return (
|
|
@@ -403,41 +582,46 @@ JWT tokens are sensitive credentials and should be handled carefully:
|
|
|
403
582
|
|
|
404
583
|
### Passing Custom State Through Authentication
|
|
405
584
|
|
|
406
|
-
You can pass custom state data through the authentication flow using the `state` parameter.
|
|
585
|
+
You can pass custom state data through the authentication flow using the `state` parameter. The state parameter is a string value that gets passed through OAuth and returned in the callback. To pass complex data, serialize it as JSON:
|
|
407
586
|
|
|
408
587
|
```ts
|
|
409
|
-
// When generating sign-in/sign-up URLs
|
|
588
|
+
// When generating sign-in/sign-up URLs, serialize your data as JSON
|
|
410
589
|
const signInUrl = await getSignInUrl({
|
|
411
|
-
state: {
|
|
590
|
+
state: JSON.stringify({
|
|
412
591
|
teamId: 'team_123',
|
|
413
592
|
feature: 'billing',
|
|
414
593
|
referrer: 'pricing-page',
|
|
415
594
|
timestamp: Date.now(),
|
|
416
|
-
},
|
|
595
|
+
}),
|
|
417
596
|
});
|
|
418
597
|
|
|
419
598
|
// The state data is available in the callback handler
|
|
420
599
|
export const GET = handleAuth({
|
|
421
600
|
onSuccess: async ({ user, state }) => {
|
|
601
|
+
// Parse the state string back to an object
|
|
602
|
+
const customData = state ? JSON.parse(state) : null;
|
|
603
|
+
|
|
422
604
|
// Access your custom state data
|
|
423
|
-
if (
|
|
424
|
-
await addUserToTeam(user.id,
|
|
605
|
+
if (customData?.teamId) {
|
|
606
|
+
await addUserToTeam(user.id, customData.teamId);
|
|
425
607
|
}
|
|
426
608
|
|
|
427
|
-
if (
|
|
428
|
-
await trackFeatureActivation(user.id,
|
|
609
|
+
if (customData?.feature) {
|
|
610
|
+
await trackFeatureActivation(user.id, customData.feature);
|
|
429
611
|
}
|
|
430
612
|
|
|
431
613
|
// Track where the user came from
|
|
432
614
|
await analytics.track('sign_in_completed', {
|
|
433
615
|
userId: user.id,
|
|
434
|
-
referrer:
|
|
435
|
-
timestamp:
|
|
616
|
+
referrer: customData?.referrer,
|
|
617
|
+
timestamp: customData?.timestamp,
|
|
436
618
|
});
|
|
437
619
|
},
|
|
438
620
|
});
|
|
439
621
|
```
|
|
440
622
|
|
|
623
|
+
> **Note**: The `state` parameter is an opaque string as defined by OAuth 2.0 (RFC 6749). If you need to pass structured data, you must serialize it yourself using `JSON.stringify()` and parse it with `JSON.parse()` in the callback.
|
|
624
|
+
|
|
441
625
|
This is useful for:
|
|
442
626
|
|
|
443
627
|
- Tracking user journey and referral sources
|
|
@@ -467,16 +651,16 @@ const { session, headers } = await authkit(request, {
|
|
|
467
651
|
});
|
|
468
652
|
```
|
|
469
653
|
|
|
470
|
-
These callbacks provide a way to perform side effects when sessions are refreshed in the middleware. Common use cases include:
|
|
654
|
+
These callbacks provide a way to perform side effects when sessions are refreshed in the proxy/middleware. Common use cases include:
|
|
471
655
|
|
|
472
656
|
- Logging authentication events
|
|
473
657
|
- Updating last activity timestamps
|
|
474
658
|
- Triggering organization-specific data prefetching
|
|
475
659
|
- Recording failed refresh attempts
|
|
476
660
|
|
|
477
|
-
### Middleware auth
|
|
661
|
+
### Proxy / Middleware auth
|
|
478
662
|
|
|
479
|
-
The default behavior of this library is to request authentication via the `withAuth` method on a per-page basis. There are some use cases where you don't want to call `withAuth` (e.g. you don't need user data for your page) or if you'd prefer a "secure by default" approach where every route defined in your middleware matcher is protected unless specified otherwise. In those cases you can opt-in to use
|
|
663
|
+
The default behavior of this library is to request authentication via the `withAuth` method on a per-page basis. There are some use cases where you don't want to call `withAuth` (e.g. you don't need user data for your page) or if you'd prefer a "secure by default" approach where every route defined in your proxy/middleware matcher is protected unless specified otherwise. In those cases you can opt-in to use `middlewareAuth` instead:
|
|
480
664
|
|
|
481
665
|
```ts
|
|
482
666
|
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
|
|
@@ -503,7 +687,7 @@ The `eagerAuth` option enables synchronous access to authentication tokens on in
|
|
|
503
687
|
|
|
504
688
|
#### How it works
|
|
505
689
|
|
|
506
|
-
When `eagerAuth: true` is set, the middleware temporarily stores the access token in a short-lived cookie (30 seconds) that is:
|
|
690
|
+
When `eagerAuth: true` is set, the proxy/middleware temporarily stores the access token in a short-lived cookie (30 seconds) that is:
|
|
507
691
|
|
|
508
692
|
- Only set on initial page loads (not API or prefetch requests)
|
|
509
693
|
- Immediately consumed and deleted by the client
|
|
@@ -511,7 +695,7 @@ When `eagerAuth: true` is set, the middleware temporarily stores the access toke
|
|
|
511
695
|
|
|
512
696
|
#### Usage
|
|
513
697
|
|
|
514
|
-
Enable eager auth in your middleware configuration:
|
|
698
|
+
Enable eager auth in your proxy/middleware configuration:
|
|
515
699
|
|
|
516
700
|
```ts
|
|
517
701
|
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
|
|
@@ -531,16 +715,18 @@ import { useAccessToken } from '@workos-inc/authkit-nextjs/components';
|
|
|
531
715
|
function MyComponent() {
|
|
532
716
|
const { getAccessToken } = useAccessToken();
|
|
533
717
|
|
|
534
|
-
|
|
535
|
-
|
|
718
|
+
async function handleClick() {
|
|
719
|
+
// Token is available immediately on initial page load
|
|
720
|
+
const token = await getAccessToken();
|
|
536
721
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
722
|
+
// Use with third-party services that need immediate token access
|
|
723
|
+
if (token) {
|
|
724
|
+
// Initialize your third-party client with the token
|
|
725
|
+
thirdPartyClient.authenticate(token);
|
|
726
|
+
}
|
|
541
727
|
}
|
|
542
728
|
|
|
543
|
-
return <
|
|
729
|
+
return <button onClick={handleClick}>Authenticate</button>;
|
|
544
730
|
}
|
|
545
731
|
```
|
|
546
732
|
|
|
@@ -565,64 +751,6 @@ Eager auth makes tokens briefly accessible via JavaScript (30-second window) to
|
|
|
565
751
|
- Most API calls where a brief loading state is acceptable
|
|
566
752
|
- When you don't need immediate token access on page load
|
|
567
753
|
|
|
568
|
-
### Composing middleware
|
|
569
|
-
|
|
570
|
-
> **Security note:** Always forward `request.headers` when returning `NextResponse.*` to mitigate SSRF issues in Next.js < 14.2.32 (14.x) or < 15.4.7 (15.x). This pattern is safe on all versions. We strongly recommend upgrading to the latest Next.js.
|
|
571
|
-
|
|
572
|
-
If you don't want to use `authkitMiddleware` and instead want to compose your own middleware, you can use the `authkit` method. In this mode you are responsible to handling what to do when there's no session on a protected route.
|
|
573
|
-
|
|
574
|
-
```ts
|
|
575
|
-
export default async function middleware(request: NextRequest) {
|
|
576
|
-
// Perform logic before or after AuthKit
|
|
577
|
-
|
|
578
|
-
// Auth object contains the session, response headers and an authorization URL in the case that the session isn't valid
|
|
579
|
-
// This method will automatically handle setting the cookie and refreshing the session
|
|
580
|
-
const {
|
|
581
|
-
session,
|
|
582
|
-
headers: authkitHeaders,
|
|
583
|
-
authorizationUrl,
|
|
584
|
-
} = await authkit(request, {
|
|
585
|
-
debug: true,
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
const { pathname } = new URL(request.url);
|
|
589
|
-
|
|
590
|
-
// Control of what to do when there's no session on a protected route is left to the developer
|
|
591
|
-
if (pathname.startsWith('/account') && !session.user) {
|
|
592
|
-
console.log('No session on protected path');
|
|
593
|
-
|
|
594
|
-
// Preserve AuthKit headers on redirects (e.g., cookies)
|
|
595
|
-
const response = NextResponse.redirect(authorizationUrl);
|
|
596
|
-
for (const [key, value] of authkitHeaders) {
|
|
597
|
-
if (key.toLowerCase() === 'set-cookie') {
|
|
598
|
-
response.headers.append(key, value);
|
|
599
|
-
} else {
|
|
600
|
-
response.headers.set(key, value);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return response;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Forward the incoming request headers (mitigation) and then add AuthKit's headers
|
|
607
|
-
const response = NextResponse.next({
|
|
608
|
-
request: { headers: new Headers(request.headers) },
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
for (const [key, value] of authkitHeaders) {
|
|
612
|
-
if (key.toLowerCase() === 'set-cookie') {
|
|
613
|
-
response.headers.append(key, value);
|
|
614
|
-
} else {
|
|
615
|
-
response.headers.set(key, value);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
return response;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Match against the pages
|
|
623
|
-
export const config = { matcher: ['/', '/account/:path*'] };
|
|
624
|
-
```
|
|
625
|
-
|
|
626
754
|
### Signing out
|
|
627
755
|
|
|
628
756
|
Use the `signOut` method to sign out the current logged in user and redirect to your app's default Logout URI. The Logout URI is set in your WorkOS dashboard settings under "Redirect".
|
|
@@ -638,6 +766,9 @@ await signOut({ returnTo: 'https://your-app.com/signed-out' });
|
|
|
638
766
|
Render the `Impersonation` component in your app so that it is clear when someone is [impersonating a user](https://workos.com/docs/user-management/impersonation).
|
|
639
767
|
The component will display a frame with some information about the impersonated user, as well as a button to stop impersonating.
|
|
640
768
|
|
|
769
|
+
> [!IMPORTANT]
|
|
770
|
+
> Impersonation requires a configured **Sign-in endpoint** in your WorkOS dashboard. See the [sign-in endpoint](#sign-in-endpoint) setup instructions. Without it, impersonation from the WorkOS dashboard will fail with a `Missing required auth parameter` error.
|
|
771
|
+
|
|
641
772
|
```jsx
|
|
642
773
|
import { Impersonation, AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
|
|
643
774
|
|
|
@@ -689,6 +820,25 @@ export default authkitMiddleware({
|
|
|
689
820
|
});
|
|
690
821
|
```
|
|
691
822
|
|
|
823
|
+
### Validate an API key
|
|
824
|
+
|
|
825
|
+
Use the `validateApiKey` function in your application's public API endpoints to parse a [Bearer Authentication](https://swagger.io/docs/specification/v3_0/authentication/bearer-authentication/) header and validate the [API key](https://workos.com/docs/authkit/api-keys) with WorkOS.
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
import { NextResponse } from 'next/server';
|
|
829
|
+
import { validateApiKey } from '@workos-inc/authkit-nextjs';
|
|
830
|
+
|
|
831
|
+
export async function GET() {
|
|
832
|
+
const { apiKey } = await validateApiKey();
|
|
833
|
+
|
|
834
|
+
if (!apiKey) {
|
|
835
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return NextResponse.json({ success: true });
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
692
842
|
### Advanced: Direct access to the WorkOS client
|
|
693
843
|
|
|
694
844
|
For advanced use cases or functionality not covered by the helper methods, you can access the underlying WorkOS client directly:
|
|
@@ -753,9 +903,35 @@ await saveSession(session, req);
|
|
|
753
903
|
await saveSession(session, 'https://example.com/callback');
|
|
754
904
|
```
|
|
755
905
|
|
|
906
|
+
### CDN Deployments and Caching
|
|
907
|
+
|
|
908
|
+
AuthKit automatically implements cache security measures to protect against session leakage in CDN environments. This is particularly important when deploying to AWS with SST/OpenNext, Cloudflare, or other CDN configurations.
|
|
909
|
+
|
|
910
|
+
#### How It Works
|
|
911
|
+
|
|
912
|
+
The library automatically sets appropriate cache headers on all authenticated requests:
|
|
913
|
+
|
|
914
|
+
- `Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0` - Aggressive cache prevention with multiple directives
|
|
915
|
+
- `Pragma: no-cache` - HTTP/1.0 compatibility
|
|
916
|
+
- `Expires: 0` - HTTP/1.0 cache expiration
|
|
917
|
+
- `Vary: Cookie` - Ensures CDNs differentiate between different users (defense-in-depth)
|
|
918
|
+
- `x-middleware-cache: no-cache` - Prevents Next.js proxy/middleware result caching
|
|
919
|
+
|
|
920
|
+
These headers are applied automatically when:
|
|
921
|
+
|
|
922
|
+
- A session cookie is present in the request
|
|
923
|
+
- An Authorization header is detected
|
|
924
|
+
- An active authenticated session exists
|
|
925
|
+
|
|
926
|
+
#### Performance Considerations
|
|
927
|
+
|
|
928
|
+
**Authenticated pages:** Will not be cached at the CDN level and will always hit your origin server. This is the correct and secure behavior for session-based authentication.
|
|
929
|
+
|
|
930
|
+
**Public pages:** Unaffected by these security measures. Public routes without authentication context can still be cached normally.
|
|
931
|
+
|
|
756
932
|
### Debugging
|
|
757
933
|
|
|
758
|
-
To enable debug logs, initialize the middleware with the debug flag enabled.
|
|
934
|
+
To enable debug logs, initialize the proxy/middleware with the debug flag enabled.
|
|
759
935
|
|
|
760
936
|
```js
|
|
761
937
|
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
|
|
@@ -763,8 +939,35 @@ import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
|
|
|
763
939
|
export default authkitMiddleware({ debug: true });
|
|
764
940
|
```
|
|
765
941
|
|
|
942
|
+
### Security
|
|
943
|
+
|
|
944
|
+
#### PKCE and CSRF protection
|
|
945
|
+
|
|
946
|
+
This library uses [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key for Code Exchange) and a sealed (encrypted) OAuth state parameter on every authorization request. The state contains a cryptographic nonce for CSRF protection per [RFC 9700](https://datatracker.ietf.org/doc/rfc9700/) and a code verifier for protection against authorization code interception. During sign-in, a short-lived `wos-auth-verifier` cookie is set containing the sealed state. This cookie is automatically cleaned up after the callback completes.
|
|
947
|
+
|
|
948
|
+
> [!NOTE]
|
|
949
|
+
> **Upgrading to v3:** PKCE is now always enabled. The `WORKOS_ENABLE_PKCE` environment variable is no longer needed and can be removed from your configuration.
|
|
950
|
+
|
|
951
|
+
#### Cookie requirements
|
|
952
|
+
|
|
953
|
+
The `wos-auth-verifier` cookie must survive the round-trip from sign-in initiation to the callback. On callback, the library verifies that the cookie is present and matches the URL `state` parameter — this two-channel check is what prevents CSRF attacks.
|
|
954
|
+
|
|
955
|
+
If the cookie is missing or doesn't match, authentication will fail with one of:
|
|
956
|
+
|
|
957
|
+
- `Auth cookie missing` — the cookie was not sent back with the callback request. This typically happens when a reverse proxy or CDN strips `Set-Cookie` headers on redirects.
|
|
958
|
+
- `OAuth state mismatch` — the cookie and URL `state` parameter don't match, indicating a possible CSRF attack or cookie corruption.
|
|
959
|
+
|
|
960
|
+
> [!IMPORTANT]
|
|
961
|
+
> **Upgrading to v3:** Previous versions would silently fall back to verifying only the URL `state` parameter when the cookie was missing. This fallback has been removed because it disabled CSRF protection. If you see `Auth cookie missing` errors after upgrading, ensure that `Set-Cookie` headers are propagated on redirects between your application and the user's browser.
|
|
962
|
+
|
|
766
963
|
### Troubleshooting
|
|
767
964
|
|
|
965
|
+
#### `Missing required auth parameter` when impersonating from the WorkOS dashboard
|
|
966
|
+
|
|
967
|
+
This error occurs when WorkOS-initiated flows (like dashboard impersonation) redirect directly to your callback URL without going through your application's sign-in flow. Because this library enforces PKCE/CSRF verification on every callback, the request is rejected when the required `state` parameter is missing.
|
|
968
|
+
|
|
969
|
+
**Fix:** Configure a [sign-in endpoint](#sign-in-endpoint) in your WorkOS dashboard so that impersonation flows route through your app first, allowing PKCE/state to be set up before redirecting to WorkOS.
|
|
970
|
+
|
|
768
971
|
#### NEXT_REDIRECT error when using try/catch blocks
|
|
769
972
|
|
|
770
973
|
Wrapping a `withAuth({ ensureSignedIn: true })` call in a try/catch block will cause a `NEXT_REDIRECT` error. This is because `withAuth` will attempt to redirect the user to AuthKit if no session is detected and redirects in Next must be [called outside a try/catch](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#redirecting).
|
package/dist/esm/actions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use server';
|
|
2
|
-
import { signOut, switchToOrganization } from './auth.js';
|
|
2
|
+
import { getSignInUrl, signOut, switchToOrganization } from './auth.js';
|
|
3
3
|
import { refreshSession, withAuth } from './session.js';
|
|
4
4
|
import { getWorkOS } from './workos.js';
|
|
5
5
|
/**
|
|
@@ -28,10 +28,28 @@ export const getOrganizationAction = async (organizationId) => {
|
|
|
28
28
|
return await getWorkOS().organizations.getOrganization(organizationId);
|
|
29
29
|
};
|
|
30
30
|
export const getAuthAction = async (options) => {
|
|
31
|
-
|
|
31
|
+
// Never pass ensureSignedIn to withAuth from a server action, because withAuth
|
|
32
|
+
// would call redirect() to an external URL, which causes CORS errors when
|
|
33
|
+
// invoked via a client-side fetch. Instead, return the sign-in URL so the
|
|
34
|
+
// client can redirect via window.location.href.
|
|
35
|
+
const auth = await withAuth();
|
|
36
|
+
const sanitized = sanitize(auth);
|
|
37
|
+
if (options?.ensureSignedIn && !auth.user) {
|
|
38
|
+
const signInUrl = await getSignInUrl();
|
|
39
|
+
return { ...sanitized, signInUrl };
|
|
40
|
+
}
|
|
41
|
+
return sanitized;
|
|
32
42
|
};
|
|
33
43
|
export const refreshAuthAction = async ({ ensureSignedIn, organizationId, }) => {
|
|
34
|
-
|
|
44
|
+
// Never pass ensureSignedIn to refreshSession from a server action for the
|
|
45
|
+
// same CORS reason as getAuthAction above.
|
|
46
|
+
const auth = await refreshSession({ organizationId });
|
|
47
|
+
const sanitized = sanitize(auth);
|
|
48
|
+
if (ensureSignedIn && !auth.user) {
|
|
49
|
+
const signInUrl = await getSignInUrl();
|
|
50
|
+
return { ...sanitized, signInUrl };
|
|
51
|
+
}
|
|
52
|
+
return sanitized;
|
|
35
53
|
};
|
|
36
54
|
export const switchToOrganizationAction = async (organizationId, options) => {
|
|
37
55
|
return sanitize(await switchToOrganization(organizationId, options));
|
|
@@ -47,9 +65,21 @@ export async function getAccessTokenAction() {
|
|
|
47
65
|
/**
|
|
48
66
|
* This action is used to refresh the access token from the auth object.
|
|
49
67
|
* It is used to fetch the access token from the server.
|
|
68
|
+
*
|
|
69
|
+
* Errors are caught and returned as data rather than thrown, to prevent
|
|
70
|
+
* Next.js from returning 500 responses for server action failures.
|
|
50
71
|
*/
|
|
51
72
|
export async function refreshAccessTokenAction() {
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
try {
|
|
74
|
+
const auth = await refreshSession();
|
|
75
|
+
return { accessToken: auth.accessToken };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.warn('Failed to refresh access token:', error instanceof Error ? error.message : String(error));
|
|
79
|
+
return {
|
|
80
|
+
accessToken: undefined,
|
|
81
|
+
error: 'Failed to refresh access token',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
54
84
|
}
|
|
55
85
|
//# sourceMappingURL=actions.js.map
|
package/dist/esm/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/actions.ts"],"names":[],"mappings":"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"}
|