Quickstart — Next.js - Authaz - Docs
Authaz Docs SDK Quickstarts Quickstart — Next.js SDK Quickstarts
Quickstart — Next.js
2 min read· Updated Apr 29, 2026
This guide gets a Next.js (App Router) app authenticating users through Authaz Universal Login. You'll use @authaz/next on the server and @authaz/react for hooks in client components.
Prerequisites#
Node.js 18+ and a Next.js 14/15 App Router app (npx create-next-app@latest if you're starting fresh).
An Authaz application. From the dashboard, grab:
Application ID → AUTHAZ_CLIENT_ID
Client Secret → AUTHAZ_CLIENT_SECRET
Organization ID → AUTHAZ_ORGANIZATION_ID
Tenant ID (optional, only if multi-tenant) → AUTHAZ_TENANT_ID
Add a redirect URI to the application: http://localhost:3000/auth/callback (and your production URL). npm install @authaz/next @authaz/react Step 2 — Environment variables# AUTHAZ_CLIENT_ID = app_01h...
AUTHAZ_CLIENT_SECRET = secret_...
AUTHAZ_ORGANIZATION_ID = 0199...
AUTHAZ_TENANT_ID = 0198... # optional Step 3 — Mount the auth handler# Create a catch-all route at app/api/auth/[...authaz]/route.ts:
import { createAuthazHandler } from "@authaz/next" ;
export const { GET , POST } = createAuthazHandler ({
clientId : process . env . AUTHAZ_CLIENT_ID ! ,
clientSecret : process . env . AUTHAZ_CLIENT_SECRET ! ,
organizationId : process . env . AUTHAZ_ORGANIZATION_ID ! ,
tenantId : process . env . AUTHAZ_TENANT_ID ,
afterLoginUrl : "/dashboard" ,
afterLogoutUrl : "/" ,
}); Route What it does GET /api/auth/loginRedirects to Universal Login with PKCE. POST /api/auth/callbackReceives the OAuth code, exchanges it, sets the session cookie. POST /api/auth/logoutClears the session and redirects. GET /api/auth/meCurrent user, or 401 if not signed in. POST /api/auth/refreshRefreshes the access token.
Step 4 — Add the OAuth callback page# Authaz redirects users back to /auth/callback as a GET, but the handler above expects a POST (so the auth code never lives in a URL bar past the first hop). Add a tiny client component that re-POSTs:
// app/auth/callback/page.tsx
"use client" ;
import { Suspense , useEffect } from "react" ;
import { useSearchParams } from "next/navigation" ;
const CallbackContent = () => {
const searchParams = useSearchParams ();
useEffect (() => {
const form = document . createElement ( "form" );
form . method = "POST" ;
form . action = "/api/auth/callback" ;
[ "code" , "state" , "error" , "error_description" ]. forEach (( param ) => {
const value = searchParams . get ( param );
if ( value ) {
const input = document . createElement ( "input" );
input . type = "hidden" ;
input . name = param ;
input . value = value ;
form . appendChild ( input );
}
});
document . body . appendChild ( form );
form . submit ();
}, [ searchParams ]);
return < p >Completing login…</ p >;
};
export default function CallbackPage () {
return (
< Suspense fallback = { < p >Loading…</ p > } >
< CallbackContent />
</ Suspense >
);
} Step 5 — Wrap the app in AuthazProvider# // app/layout.tsx
import { AuthazProvider } from "@authaz/react" ;
export default function RootLayout ({ children }: { children : React . ReactNode }) {
return (
< html lang = "en" >
< body >
< AuthazProvider basePath = "/api/auth" autoRefresh >
{ children }
</ AuthazProvider >
</ body >
</ html >
);
} autoRefresh re-fetches the access token automatically on a 401 — most apps want this.
A client component that shows login state and a button:
// app/components/AuthButton.tsx
"use client" ;
import { useAuthaz } from "@authaz/react" ;
export const AuthButton = () => {
const { isAuthenticated , isLoading , user , login , logout } = useAuthaz ();
if ( isLoading ) return < span >…</ span >;
return isAuthenticated ? (
< div >
< span > { user ?. email } </ span >
< button onClick = { () => logout () } >Logout</ button >
</ div >
) : (
< button onClick = { () => login () } >Login</ button >
);
}; Two ways. Pick the one that fits the page:
Client-component protection (redirects if not signed in):
// app/dashboard/page.tsx
"use client" ;
import { useRequireAuth } from "@authaz/react" ;
export default function Dashboard () {
useRequireAuth ();
return < h1 >Dashboard</ h1 >;
} Server-component protection (redirects before any HTML is rendered):
// app/dashboard/page.tsx
import { requireAuth } from "@authaz/next" ;
export default async function Dashboard () {
await requireAuth ();
return < h1 >Dashboard</ h1 >;
} Step 8 — Protect API routes# // app/api/protected/route.ts
import { withAuth } from "@authaz/next" ;
import { NextResponse } from "next/server" ;
export const GET = withAuth ( async () => {
return NextResponse . json ({ secret : "only signed-in users see this" });
}); Hit http://localhost:3000, click Login , sign up at Universal Login, and you'll land on /dashboard with useAuthaz() returning a populated user.
Middleware route guards — use createAuthMiddleware in middleware.ts to redirect unauthenticated users before page handlers run.
Server Components with the user object — requireUser({ authazDomain, apiKey }) returns a helper whose getOrRedirect() resolves to the current user.
Custom domains — pass authazIdentityDomain: "https://auth.yourapp.com" to createAuthazHandler once your custom domain is verified.