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-next

Environment Variables

Add the following environment variables to your .env.local file:

VariableRequiredDescription
TRANSACTIONAL_AUTH_DOMAINYesYour Transactional Auth domain (e.g., auth.usetransactional.com)
TRANSACTIONAL_AUTH_CLIENT_IDYesThe Client ID from your Transactional Auth application
TRANSACTIONAL_AUTH_CLIENT_SECRETServer-sideThe Client Secret from your Transactional Auth application
TRANSACTIONAL_AUTH_BASE_URLYesYour 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:3000

Route 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:

RouteDescription
/api/auth/loginRedirects the user to the Transactional Auth login page
/api/auth/callbackHandles the OAuth callback and sets the session cookie
/api/auth/logoutClears the session and redirects to the logout endpoint
/api/auth/sessionReturns 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:

PropertyTypeDescription
session.userTransactionalAuthUserThe authenticated user object
session.accessTokenstringThe current access token
session.expiresAtnumberToken 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:

PropertyTypeDescription
userTransactionalAuthUser | nullThe authenticated user, or null
isLoadingbooleantrue while the session is being fetched
isAuthenticatedbooleantrue if the user has a valid session
login(options?: LoginOptions) => voidRedirects to the login page
logout(options?: LogoutOptions) => voidLogs 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

OptionTypeDefaultDescription
protectedPathsstring[][]Glob patterns for routes that require authentication
publicPathsstring[][]Glob patterns for routes that are always accessible
loginUrlstring"/api/auth/login"URL to redirect unauthenticated users to
onUnauthorized(req: NextRequest) => Response | voidundefinedOptional 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

OptionTypeDefaultDescription
domainstringenv varYour Transactional Auth domain
clientIdstringenv varYour application Client ID
clientSecretstringenv varYour application Client Secret
baseUrlstringenv varYour application base URL
scopestring"openid profile email"OAuth scopes to request
audiencestringundefinedAPI audience for access token
cookieNamestring"txauth.session"Session cookie name
cookieOptions.securebooleantrue in productionSet the Secure flag on the cookie
cookieOptions.sameSite"lax" | "strict" | "none""lax"SameSite cookie attribute
cookieOptions.maxAgenumber604800 (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

PropertyTypeDescription
substringUnique user identifier
emailstringUser email address
namestring | undefinedUser display name
picturestring | undefinedUser avatar URL
email_verifiedbooleanWhether the email is verified

Session

PropertyTypeDescription
userTransactionalAuthUserThe authenticated user
accessTokenstringCurrent access token
expiresAtnumberToken expiration (Unix timestamp)

LoginOptions

PropertyTypeDescription
returnTostring | undefinedURL to redirect to after login
scopestring | undefinedOverride default scopes

LogoutOptions

PropertyTypeDescription
returnTostring | undefinedURL to redirect to after logout

Requirements

  • Next.js 14.0 or later (App Router)
  • React 18 or later
  • Node.js 18 or later