
Product
Introducing Socket Firewall Enterprise: Flexible, Configurable Protection for Modern Package Ecosystems
Socket Firewall Enterprise is now available with flexible deployment, configurable policies, and expanded language support.
@workos-inc/authkit-nextjs
Advanced tools
Authentication and session helpers for using WorkOS & AuthKit with Next.js
The AuthKit library for Next.js provides convenient helpers for authentication and session management using WorkOS & AuthKit with Next.js.
Note: This library is intended for use with the Next.js App Router.
Install the package with:
npm i @workos-inc/authkit-nextjs
or
yarn add @workos-inc/authkit-nextjs
Make sure the following values are present in your .env.local environment variables file. The client ID and API key can be found in the WorkOS dashboard, and the redirect URI can also be configured there.
WORKOS_CLIENT_ID="client_..." # retrieved from the WorkOS dashboard
WORKOS_API_KEY="sk_test_..." # retrieved from the WorkOS dashboard
WORKOS_COOKIE_PASSWORD="<your password>" # generate a secure password here
NEXT_PUBLIC_WORKOS_REDIRECT_URI="http://localhost:3000/callback" # configured in the WorkOS dashboard
WORKOS_COOKIE_PASSWORD is the private key used to encrypt the session cookie. It has to be at least 32 characters long. You can use the 1Password generator or the openssl library to generate a strong password via the command line:
openssl rand -base64 24
To use the signOut method, you'll need to set a default Logout URI in your WorkOS dashboard settings under "Redirects".
Certain environment variables are optional and can be used to debug or configure cookie settings.
| Environment Variable | Default Value | Description |
|---|---|---|
WORKOS_COOKIE_MAX_AGE | 34560000 (400 days) | Maximum age of the cookie in seconds |
WORKOS_COOKIE_DOMAIN | None | Domain for the cookie. When empty, the cookie is only valid for the current domain |
WORKOS_COOKIE_NAME | 'wos-session' | Name of the session cookie |
WORKOS_API_HOSTNAME | 'api.workos.com' | Base WorkOS API URL |
WORKOS_API_HTTPS | true | Whether to use HTTPS in API calls |
WORKOS_API_PORT | None | Port to use for API calls. When not set, uses standard ports (443 for HTTPS, 80 for HTTP) |
WORKOS_COOKIE_SAMESITE | 'lax' | SameSite attribute for cookies. Options: 'lax', 'strict', or 'none' |
Example usage:
WORKOS_COOKIE_MAX_AGE='600'
WORKOS_COOKIE_DOMAIN='example.com'
WORKOS_COOKIE_NAME='my-auth-cookie'
[!WARNING] Setting
WORKOS_COOKIE_SAMESITE='none'allows cookies to be sent in cross-origin contexts (like iframes), but reduces protection against CSRF attacks. This setting forces cookies to be secure (HTTPS only) and should only be used when absolutely necessary for your application architecture.
[!TIP] >
WORKOS_COOKIE_DOMAINcan be used to share WorkOS sessions between apps/domains. Note: TheWORKOS_COOKIE_PASSWORDwould need to be the same across apps/domains. Not needed for most use cases.
WorkOS requires that you have a callback URL to redirect users back to after they've authenticated. In your Next.js app, expose an API route and add the following.
import { handleAuth } from '@workos-inc/authkit-nextjs';
export const GET = handleAuth();
Make sure this route matches the WORKOS_REDIRECT_URI variable and the configured redirect URI in your WorkOS dashboard. For instance if your redirect URI is http://localhost:3000/auth/callback then you'd put the above code in /app/auth/callback/route.ts.
You can also control the pathname the user will be sent to after signing-in by passing a returnPathname option to handleAuth like so:
export const GET = handleAuth({ returnPathname: '/dashboard' });
If your application needs to persist data upon a successful authentication, like the oauthTokens from an upstream provider, you can pass in a onSuccess function that will get called after the user has successfully authenticated:
export const GET = handleAuth({
onSuccess: async ({ user, oauthTokens, authenticationMethod, organizationId, state }) => {
await saveTokens(oauthTokens);
if (authenticationMethod) {
await saveAuthMethod(user.id, authenticationMethod);
}
// Access custom state data passed through the auth flow
if (state?.teamId) {
await addUserToTeam(user.id, state.teamId);
}
},
});
When running in environments like Docker, set the baseURL explicitly to ensure the redirects point to the correct location.
export const GET = handleAuth({
baseURL: 'http://localhost:3000',
});
handleAuth can be used with the following options.
| Option | Default | Description |
|---|---|---|
returnPathname | / | The pathname to redirect the user to after signing in |
baseURL | undefined | The base URL to use for the redirect URI instead of the one in the request. Required if the app is being run in a container like docker where the hostname can be different from the one in the request |
onSuccess | undefined | A function that receives successful authentication data and can be used for side-effects like persisting tokens |
onError | undefined | A function that can receive the error and the request and handle the error in its own way. |
The onSuccess callback receives the following data:
| Property | Type | Description |
|---|---|---|
user | User | The authenticated user object |
accessToken | string | JWT access token |
refreshToken | string | Refresh token for session renewal |
impersonator | Impersonator | undefined | Present if user is being impersonated |
oauthTokens | OauthTokens | undefined | OAuth tokens from upstream provider |
authenticationMethod | string | undefined | How the user authenticated (e.g., 'password', 'google-oauth'). Only available during initial login |
organizationId | string | undefined | Organization context of authentication |
state | Record<string, any> | undefined | Custom state data passed through the authentication flow |
Note: authenticationMethod is only provided during the initial authentication callback. It will not be available in subsequent requests or session refreshes.
This library relies on Next.js middleware to provide session management for routes. Put the following in your middleware.ts file in the root of your project:
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware();
// Match against pages that require auth
// Leave this out if you want auth on every resource (including images, css etc.)
export const config = { matcher: ['/', '/admin'] };
The middleware can be configured with several options.
| Option | Default | Description |
|---|---|---|
redirectUri | undefined | Used in cases where you need your redirect URI to be set dynamically (e.g. Vercel preview deployments) |
middlewareAuth | undefined | Used to configure middleware auth options. See middleware auth for more details. |
debug | false | Enables debug logs. |
signUpPaths | [] | Used to specify paths that should use the 'sign-up' screen hint when redirecting to AuthKit. |
eagerAuth | false | Enables synchronous access token availability for third-party services. See eager auth for more details. |
In cases where you need your redirect URI to be set dynamically (e.g. Vercel preview deployments), use the redirectUri option in authkitMiddleware:
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware({
redirectUri: 'https://foo.example.com/callback',
});
// Match against pages that require auth
// Leave this out if you want auth on every resource (including images, css etc.)
export const config = { matcher: ['/', '/admin'] };
Custom redirect URIs will be used over a redirect URI configured in the environment variables.
AuthKitProviderUse AuthKitProvider to wrap your app layout, which provides client side auth methods adds protections for auth edge cases.
import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AuthKitProvider>{children}</AuthKitProvider>
</body>
</html>
);
}
For pages where you want to display a signed-in and signed-out view, use withAuth to retrieve the user session from WorkOS.
import Link from 'next/link';
import { getSignInUrl, getSignUpUrl, withAuth, signOut } from '@workos-inc/authkit-nextjs';
export default async function HomePage() {
// Retrieves the user from the session or returns `null` if no user is signed in
const { user } = await withAuth();
if (!user) {
// Get the URL to redirect the user to AuthKit to sign in
const signInUrl = await getSignInUrl();
// Get the URL to redirect the user to AuthKit to sign up
const signUpUrl = await getSignUpUrl();
// You can also pass custom state data through the auth flow
const signInUrlWithState = await getSignInUrl({
state: {
teamId: 'team_123',
referrer: 'homepage',
},
});
return (
<>
<Link href={signInUrl}>Log in</Link>
<Link href={signUpUrl}>Sign Up</Link>
</>
);
}
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<p>Welcome back {user?.firstName && `, ${user?.firstName}`}</p>
<button type="submit">Sign out</button>
</form>
);
}
For client components, use the useAuth hook to get the current user session.
'use client';
// Note the updated import path
import { useAuth } from '@workos-inc/authkit-nextjs/components';
export default function MyComponent() {
// Retrieves the user from the session or returns `null` if no user is signed in
const { user, loading } = useAuth();
if (loading) {
return <div>Loading...</div>;
}
return <div>{user?.firstName}</div>;
}
For situations where you need access to the authenticated user's currently active feature flags, use withAuth to retrieve the flags from the WorkOS session.
const { featureFlags } = await withAuth();
For pages where a signed-in user is mandatory, you can use the ensureSignedIn option:
// Server component
const { user } = await withAuth({ ensureSignedIn: true });
// Client component
const { user, loading } = useAuth({ ensureSignedIn: true });
Enabling ensureSignedIn will redirect users to AuthKit if they attempt to access the page without being authenticated.
Use the refreshSession method in a server action or route handler to fetch the latest session details, including any changes to the user's roles or permissions.
The organizationId parameter can be passed to refreshSession in order to switch the session to a different organization. If the current session is not authorized for the next organization, an appropriate authentication error will be returned.
In client components, you can refresh the session with the refreshAuth hook.
'use client';
import { useAuth } from '@workos-inc/authkit-nextjs/components';
import React, { useEffect } from 'react';
export function SwitchOrganizationButton() {
const { user, organizationId, loading, refreshAuth } = useAuth();
useEffect(() => {
// This will log out the new organizationId after refreshing the session
console.log('organizationId', organizationId);
}, [organizationId]);
if (loading) {
return <div>Loading...</div>;
}
const handleRefreshSession = async () => {
const result = await refreshAuth({
// Provide the organizationId to switch to
organizationId: 'org_123',
});
if (result?.error) {
console.log('Error refreshing session:', result.error);
}
};
if (user) {
return <button onClick={handleRefreshSession}>Refresh session</button>;
} else {
return <div>Not signed in</div>;
}
}
This library provides a useAccessToken hook for client-side access token management with automatic refresh functionality.
Use this hook when you need direct access to the JWT token for:
function ApiClient() {
const { accessToken, loading, error, refresh } = useAccessToken();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!accessToken) return <div>Not authenticated</div>;
return (
<div>
<p>Token available: {accessToken.substring(0, 10)}...</p>
<button onClick={refresh}>Refresh token</button>
</div>
);
}
| Property | Type | Description |
|---|---|---|
accessToken | string | undefined | The current access token |
loading | boolean | True when token is being fetched or refreshed |
error | Error | null | Error during token fetch/refresh, or null |
refresh | () => Promise<string | undefined> | Manually refresh the token |
The useAccessToken hook automatically synchronizes with the main authentication session. When you call refreshAuth() from useAuth, the access token will update accordingly. Similarly, using the refresh() method from useAccessToken will update the entire authentication session.
JWT tokens are sensitive credentials and should be handled carefully:
You can pass custom state data through the authentication flow using the state parameter. This data will be available in the onSuccess callback after authentication:
// When generating sign-in/sign-up URLs
const signInUrl = await getSignInUrl({
state: {
teamId: 'team_123',
feature: 'billing',
referrer: 'pricing-page',
timestamp: Date.now(),
},
});
// The state data is available in the callback handler
export const GET = handleAuth({
onSuccess: async ({ user, state }) => {
// Access your custom state data
if (state?.teamId) {
await addUserToTeam(user.id, state.teamId);
}
if (state?.feature) {
await trackFeatureActivation(user.id, state.feature);
}
// Track where the user came from
await analytics.track('sign_in_completed', {
userId: user.id,
referrer: state?.referrer,
timestamp: state?.timestamp,
});
},
});
This is useful for:
When using the authkit function directly, you can provide callbacks to be notified when a session is refreshed:
const { session, headers } = await authkit(request, {
onSessionRefreshSuccess: async ({ accessToken, user, impersonator }) => {
// Log successful refresh
console.log(`Session refreshed for ${user.email}.`);
},
onSessionRefreshError: async ({ error, request }) => {
// Log refresh failure
console.error('Session refresh failed:', error);
// Notify monitoring system
await notifyMonitoring('session_refresh_failed', {
url: request.url,
error: error.message,
});
},
});
These callbacks provide a way to perform side effects when sessions are refreshed in the middleware. Common use cases include:
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 middleware auth instead:
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware({
middlewareAuth: {
enabled: true,
unauthenticatedPaths: ['/', '/about'],
},
});
// Match against pages that require auth
// Leave this out if you want auth on every resource (including images, css etc.)
export const config = { matcher: ['/', '/admin/:path*', '/about'] };
In the above example the /admin page will require a user to be signed in, whereas / and /about can be accessed without signing in.
unauthenticatedPaths uses the same glob logic as the Next.js matcher.
The eagerAuth option enables synchronous access to authentication tokens on initial page load, which is required by some third-party services that validate tokens directly with WorkOS. When enabled, tokens are available immediately without requiring an asynchronous fetch.
When eagerAuth: true is set, the middleware temporarily stores the access token in a short-lived cookie (30 seconds) that is:
Enable eager auth in your middleware configuration:
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware({
eagerAuth: true,
});
Then access the token synchronously in your client components:
'use client';
import { useAccessToken } from '@workos-inc/authkit-nextjs/components';
function MyComponent() {
const { getAccessToken } = useAccessToken();
// Token is available immediately on initial page load
const token = getAccessToken();
// Use with third-party services that need immediate token access
if (token) {
// Initialize your third-party client with the token
thirdPartyClient.authenticate(token);
}
return <div>...</div>;
}
Eager auth makes tokens briefly accessible via JavaScript (30-second window) to enable synchronous access. This is a common pattern used by many authentication libraries and is generally safe with standard XSS protections.
Best practices:
getAccessToken() method when synchronous access isn't requiredWhen to use:
When to use standard async tokens:
Security note: Always forward
request.headerswhen returningNextResponse.*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.
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.
export default async function middleware(request: NextRequest) {
// Perform logic before or after AuthKit
// Auth object contains the session, response headers and an authorization URL in the case that the session isn't valid
// This method will automatically handle setting the cookie and refreshing the session
const {
session,
headers: authkitHeaders,
authorizationUrl,
} = await authkit(request, {
debug: true,
});
const { pathname } = new URL(request.url);
// Control of what to do when there's no session on a protected route is left to the developer
if (pathname.startsWith('/account') && !session.user) {
console.log('No session on protected path');
// Preserve AuthKit headers on redirects (e.g., cookies)
const response = NextResponse.redirect(authorizationUrl);
for (const [key, value] of authkitHeaders) {
if (key.toLowerCase() === 'set-cookie') {
response.headers.append(key, value);
} else {
response.headers.set(key, value);
}
}
return response;
}
// Forward the incoming request headers (mitigation) and then add AuthKit's headers
const response = NextResponse.next({
request: { headers: new Headers(request.headers) },
});
for (const [key, value] of authkitHeaders) {
if (key.toLowerCase() === 'set-cookie') {
response.headers.append(key, value);
} else {
response.headers.set(key, value);
}
}
return response;
}
// Match against the pages
export const config = { matcher: ['/', '/account/:path*'] };
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".
To use a non-default Logout URI, you can use the returnTo parameter.
await signOut({ returnTo: 'https://your-app.com/signed-out' });
Render the Impersonation component in your app so that it is clear when someone is impersonating a user.
The component will display a frame with some information about the impersonated user, as well as a button to stop impersonating.
import { Impersonation, AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
export default function App() {
return (
<div>
<AuthKitProvider>
<Impersonation />
{/* Your app content */}
</AuthKitProvider>
</div>
);
}
Sometimes it is useful to obtain the access token directly, for instance to make API requests to another service.
import { withAuth } from '@workos-inc/authkit-nextjs';
export default async function HomePage() {
const { accessToken } = await withAuth();
if (!accessToken) {
return <div>Not signed in</div>;
}
const serviceData = await fetch('/api/path', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return <div>{serviceData}</div>;
}
The signUpPaths option can be passed to authkitMiddleware to specify paths that should use the 'sign-up' screen hint when redirecting to AuthKit. This is useful for cases where you want a path that mandates authentication to be treated as a sign up page.
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware({
signUpPaths: ['/account/sign-up', '/dashboard/:path*'],
});
For advanced use cases or functionality not covered by the helper methods, you can access the underlying WorkOS client directly:
import { getWorkOS } from '@workos-inc/authkit-nextjs';
// Get the configured WorkOS client instance
const workos = getWorkOS();
// Use any WorkOS SDK method
const organizations = await workos.organizations.listOrganizations({
limit: 10,
});
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.
For these scenarios, you can use the saveSession function:
import { saveSession } from '@workos-inc/authkit-nextjs';
import { getWorkOS } from '@workos-inc/authkit-nextjs';
// Example: Email verification flow
async function handleEmailVerification(req) {
const { code } = await req.json();
// Authenticate with the WorkOS API directly
const authResponse = await getWorkOS().userManagement.authenticateWithEmailVerification({
clientId: process.env.WORKOS_CLIENT_ID,
code,
});
// Save the session data to a cookie
await saveSession(
{
accessToken: authResponse.accessToken,
refreshToken: authResponse.refreshToken,
user: authResponse.user,
impersonator: authResponse.impersonator,
},
req,
);
return Response.redirect('/dashboard');
}
[!NOTE] This is an advanced API intended for specific integration scenarios, such as those users using self-hosted AuthKit. If you're using hosted AuthKit you should not need this.
The saveSession function accepts either a NextRequest object or a URL string as its second parameter.
// With NextRequest
await saveSession(session, req);
// With URL string
await saveSession(session, 'https://example.com/callback');
To enable debug logs, initialize the middleware with the debug flag enabled.
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';
export default authkitMiddleware({ debug: true });
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.
You may encounter this error if you attempt to import server side code from authkit-nextjs into a client component. Likely you are using withAuth in a client component instead of the useAuth hook. Either move the code to a server component or use the useAuth hook.
FAQs
Authentication and session helpers for using WorkOS & AuthKit with Next.js
The npm package @workos-inc/authkit-nextjs receives a total of 36,610 weekly downloads. As such, @workos-inc/authkit-nextjs popularity was classified as popular.
We found that @workos-inc/authkit-nextjs demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 9 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket Firewall Enterprise is now available with flexible deployment, configurable policies, and expanded language support.

Security News
Open source dashboard CNAPulse tracks CVE Numbering Authorities’ publishing activity, highlighting trends and transparency across the CVE ecosystem.

Product
Detect malware, unsafe data flows, and license issues in GitHub Actions with Socket’s new workflow scanning support.