Auth for Next.js
Full-stack authentication SDK for Next.js with App Router, Server Components, and Middleware support.
The @usetransactional/auth-next SDK provides full-stack authentication for Next.js applications. It supports the App Router, Server Components, Server Actions, Route Handlers, and Middleware with automatic session management, token refresh, and PKCE-secured login flows.
Installation
# npm
npm install @usetransactional/auth-next
# pnpm
pnpm add @usetransactional/auth-next
# yarn
yarn add @usetransactional/auth-nextEnvironment Variables
Add the following environment variables to your .env.local file:
| Variable | Required | Description |
|---|---|---|
TRANSACTIONAL_AUTH_DOMAIN | Yes | Your Transactional Auth domain (e.g., auth.usetransactional.com) |
TRANSACTIONAL_AUTH_CLIENT_ID | Yes | The Client ID from your Transactional Auth application |
TRANSACTIONAL_AUTH_CLIENT_SECRET | Server-side | The Client Secret from your Transactional Auth application |
TRANSACTIONAL_AUTH_BASE_URL | Yes | Your application URL (e.g., http://localhost:3000) |
# .env.local
TRANSACTIONAL_AUTH_DOMAIN=auth.usetransactional.com
TRANSACTIONAL_AUTH_CLIENT_ID=your_client_id
TRANSACTIONAL_AUTH_CLIENT_SECRET=your_client_secret
TRANSACTIONAL_AUTH_BASE_URL=http://localhost:3000Route Handlers
Create the authentication route handlers at app/api/auth/[...auth]/route.ts. These handle login, callback, logout, and session endpoints:
import {
handleLogin,
handleCallback,
handleLogout,
handleSession,
} from "@usetransactional/auth-next/server";
import { NextRequest, NextResponse } from "next/server";
async function handler(
req: NextRequest,
{ params }: { params: Promise<{ auth: string[] }> }
) {
const { auth } = await params;
const route = auth[0];
switch (route) {
case "login":
return handleLogin(req);
case "callback":
return handleCallback(req);
case "logout":
return handleLogout(req);
case "session":
return handleSession(req);
default:
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
}
export { handler as GET, handler as POST };This creates the following routes:
| Route | Description |
|---|---|
/api/auth/login | Redirects the user to the Transactional Auth login page |
/api/auth/callback | Handles the OAuth callback and sets the session cookie |
/api/auth/logout | Clears the session and redirects to the logout endpoint |
/api/auth/session | Returns the current session as JSON (used by client-side hooks) |
Auth Provider
Wrap your application with the TransactionalAuthProvider in your root layout to enable client-side authentication hooks:
import { TransactionalAuthProvider } from "@usetransactional/auth-next/client";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<TransactionalAuthProvider>{children}</TransactionalAuthProvider>
</body>
</html>
);
}Server Components
The SDK provides several async functions for accessing authentication state in Server Components and Server Actions.
getSession
Returns the full session object, or null if the user is not authenticated:
import { getSession } from "@usetransactional/auth-next/server";
export default async function DashboardPage() {
const session = await getSession();
if (!session) {
return <p>Not authenticated.</p>;
}
return (
<div>
<p>Welcome, {session.user.name}</p>
<p>Email: {session.user.email}</p>
<p>Token expires at: {session.expiresAt}</p>
</div>
);
}The session object contains:
| Property | Type | Description |
|---|---|---|
session.user | TransactionalAuthUser | The authenticated user object |
session.accessToken | string | The current access token |
session.expiresAt | number | Token expiration timestamp (Unix epoch) |
getUser
Returns just the user object, or null if not authenticated:
import { getUser } from "@usetransactional/auth-next/server";
export default async function ProfilePage() {
const user = await getUser();
if (!user) {
return <p>Not authenticated.</p>;
}
return (
<div>
<p>User ID: {user.sub}</p>
<p>Email: {user.email}</p>
<p>Name: {user.name}</p>
</div>
);
}getAccessToken
Returns the current access token string, useful for making authenticated API calls from the server:
import { getAccessToken } from "@usetransactional/auth-next/server";
export default async function ApiPage() {
const accessToken = await getAccessToken();
if (!accessToken) {
return <p>Not authenticated.</p>;
}
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await response.json();
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}isAuthenticated
Returns a boolean indicating whether the user has a valid session:
import { isAuthenticated } from "@usetransactional/auth-next/server";
import { redirect } from "next/navigation";
export default async function ProtectedPage() {
const authenticated = await isAuthenticated();
if (!authenticated) {
redirect("/api/auth/login");
}
return <p>You are authenticated.</p>;
}Client Components
The SDK provides React hooks for accessing authentication state in Client Components.
useAuth
The useAuth hook provides full access to the authentication state and actions:
"use client";
import { useAuth } from "@usetransactional/auth-next/client";
export function UserMenu() {
const { user, isLoading, isAuthenticated, login, logout, refreshSession } =
useAuth();
if (isLoading) {
return <p>Loading...</p>;
}
if (!isAuthenticated) {
return <button onClick={() => login()}>Log in</button>;
}
return (
<div>
<p>Hello, {user?.name}</p>
<button onClick={() => logout()}>Log out</button>
<button onClick={() => refreshSession()}>Refresh session</button>
</div>
);
}The useAuth hook returns:
| Property | Type | Description |
|---|---|---|
user | TransactionalAuthUser | null | The authenticated user, or null |
isLoading | boolean | true while the session is being fetched |
isAuthenticated | boolean | true if the user has a valid session |
login | (options?: LoginOptions) => void | Redirects to the login page |
logout | (options?: LogoutOptions) => void | Logs the user out and clears the session |
refreshSession | () => Promise<void> | Manually refreshes the session from the server |
useUser
The useUser hook is a lighter alternative when you only need the user object:
"use client";
import { useUser } from "@usetransactional/auth-next/client";
export function Avatar() {
const { user, isLoading } = useUser();
if (isLoading) {
return <div>Loading...</div>;
}
return <span>{user?.email}</span>;
}Login Button Example
A complete login/logout button component:
"use client";
import { useAuth } from "@usetransactional/auth-next/client";
export function LoginButton() {
const { isAuthenticated, isLoading, login, logout } = useAuth();
if (isLoading) {
return <button disabled>Loading...</button>;
}
if (isAuthenticated) {
return <button onClick={() => logout()}>Log out</button>;
}
return <button onClick={() => login()}>Log in</button>;
}Middleware
Use the createAuthMiddleware function to protect routes at the edge. This runs before your pages render and redirects unauthenticated users to the login page.
import { createAuthMiddleware } from "@usetransactional/auth-next/middleware";
export default createAuthMiddleware({
protectedPaths: ["/dashboard/*", "/settings/*", "/api/protected/*"],
publicPaths: ["/", "/about", "/pricing", "/api/auth/*"],
loginUrl: "/api/auth/login",
onUnauthorized: (req) => {
// Optional: custom handling for unauthorized requests
// Return a Response to override the default redirect
console.log("Unauthorized access attempt:", req.nextUrl.pathname);
},
});
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};Middleware Options
| Option | Type | Default | Description |
|---|---|---|---|
protectedPaths | string[] | [] | Glob patterns for routes that require authentication |
publicPaths | string[] | [] | Glob patterns for routes that are always accessible |
loginUrl | string | "/api/auth/login" | URL to redirect unauthenticated users to |
onUnauthorized | (req: NextRequest) => Response | void | undefined | Optional callback for custom unauthorized handling |
Path patterns support glob-style matching: /dashboard/* matches /dashboard/settings but not /dashboard/settings/billing. Use /dashboard/** to match all nested paths.
API Route Protection
Use the withAuth higher-order function to protect individual API routes:
import { withAuth } from "@usetransactional/auth-next/middleware";
import { NextResponse } from "next/server";
export const GET = withAuth(async (req, { user, accessToken }) => {
// user and accessToken are guaranteed to be present
const data = await fetchUserData(user.sub);
return NextResponse.json({ data });
});The withAuth wrapper automatically returns a 401 Unauthorized response if the user is not authenticated. The handler receives the authenticated user and access token as the second argument.
Programmatic Configuration
If you prefer to configure the SDK programmatically instead of using environment variables, use initTransactionalAuth:
import { initTransactionalAuth } from "@usetransactional/auth-next/server";
initTransactionalAuth({
domain: "auth.usetransactional.com",
clientId: "your_client_id",
clientSecret: "your_client_secret",
baseUrl: "http://localhost:3000",
scope: "openid profile email",
audience: "https://api.yourdomain.com",
cookieName: "txauth.session",
cookieOptions: {
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7, // 7 days
},
});Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
domain | string | env var | Your Transactional Auth domain |
clientId | string | env var | Your application Client ID |
clientSecret | string | env var | Your application Client Secret |
baseUrl | string | env var | Your application base URL |
scope | string | "openid profile email" | OAuth scopes to request |
audience | string | undefined | API audience for access token |
cookieName | string | "txauth.session" | Session cookie name |
cookieOptions.secure | boolean | true in production | Set the Secure flag on the cookie |
cookieOptions.sameSite | "lax" | "strict" | "none" | "lax" | SameSite cookie attribute |
cookieOptions.maxAge | number | 604800 (7 days) | Cookie max age in seconds |
TypeScript Types
The SDK exports all types for full type safety:
import type {
TransactionalAuthConfig,
TransactionalAuthUser,
Session,
LoginOptions,
LogoutOptions,
} from "@usetransactional/auth-next";TransactionalAuthConfig
The configuration object passed to initTransactionalAuth.
TransactionalAuthUser
| Property | Type | Description |
|---|---|---|
sub | string | Unique user identifier |
email | string | User email address |
name | string | undefined | User display name |
picture | string | undefined | User avatar URL |
email_verified | boolean | Whether the email is verified |
Session
| Property | Type | Description |
|---|---|---|
user | TransactionalAuthUser | The authenticated user |
accessToken | string | Current access token |
expiresAt | number | Token expiration (Unix timestamp) |
LoginOptions
| Property | Type | Description |
|---|---|---|
returnTo | string | undefined | URL to redirect to after login |
scope | string | undefined | Override default scopes |
LogoutOptions
| Property | Type | Description |
|---|---|---|
returnTo | string | undefined | URL to redirect to after logout |
Requirements
- Next.js 14.0 or later (App Router)
- React 18 or later
- Node.js 18 or later
On This Page
- Installation
- Environment Variables
- Route Handlers
- Auth Provider
- Server Components
- getSession
- getUser
- getAccessToken
- isAuthenticated
- Client Components
- useAuth
- useUser
- Login Button Example
- Middleware
- Middleware Options
- API Route Protection
- Programmatic Configuration
- Configuration Options
- TypeScript Types
- TransactionalAuthConfig
- TransactionalAuthUser
- Session
- LoginOptions
- LogoutOptions
- Requirements