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.
WORKOS_COOKIE_MAX_AGE='600' # maximum age of the cookie in seconds. Defaults to 400 days, the maximum allowed in Chrome
WORKOS_COOKIE_DOMAIN='example.com'
WORKOS_COOKIE_NAME='authkit-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=3000 # port to use for API calls
WORKOS_COOKIE_DOMAIN
can be used to share WorkOS sessions between apps/domains.
Note: The WORKOS_COOKIE_PASSWORD
would 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 ({ oauthTokens }) => {
await saveTokens(oauthTokens);
},
});
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. Useful 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 |
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. |
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.
Use 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();
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.
// 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 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>;
}
}
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.
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 auhorization 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, authorizationUrl } = await authkit(request, {
debug: true,
});
// Control of what to do when there's no session on a protected route is left to the developer
if (request.url.includes('/account') && !session.user) {
console.log('No session on protected path');
return NextResponse.redirect(authorizationUrl);
// Alternatively you could redirect to your own login page, for example if you want to use your own UI instead of hosted AuthKit
return NextResponse.redirect('/login');
}
// Headers from the authkit response need to be included in every non-redirect response to ensure that `withAuth` works as expected
return NextResponse.next({
headers: headers,
});
}
// 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*'],
});
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.
Module build failed: UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins (Unhandled scheme).
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.