Authaz logoAuthaz
DocumentationAPI Reference
  • Get Started

    • Authaz
    • Core Concepts
    • Set up your app
    • Quickstart — cURL
  • Authentication

    • Authentication Settings
    • Signup
    • Invitations
    • Password Authentication
    • Multi-Factor Auth
    • Magic Link
    • OAuth / Social Login
    • Passkey (WebAuthn)
    • SAML SSO
    • Machine-to-Machine (M2M)
    • API Keys
  • Authorization

    • Authorization
    • Resources
    • Policies
    • Roles
    • Access Explorer
  • Tenancy

    • Multi-tenancy
    • Tenancy Customization
  • Brand & Host

    • Branding
    • Custom Domains
    • Communications & Email Templates
  • Operate

    • Users
    • Analytics
    • Audit Logs
    • Application Settings
  • SDK Quickstarts

    • Quickstart — Next.js
    • Quickstart — React SPA
    • Quickstart — Hono
    • Quickstart — .NET (Authaz.Sdk)
  • Recipes

    • Recipes & Cookbook
    • Next.js — first integration
    • Next.js — B2B SaaS (multi-tenant)
    • Hono — first integration
    • Hono — B2B SaaS (multi-tenant)
    • React SPA — first integration
    • React SPA — B2B SaaS (multi-tenant)
    • .NET — first integration
    • .NET — B2B SaaS (multi-tenant)
  • Reference

    • Tokens
    • API Reference
    • Errors & Troubleshooting
  • Documentation

    • How Authaz is Built
  1. Authaz
  2. Docs
  3. SDK Quickstarts
  4. Quickstart — Next.js

SDK Quickstarts

Quickstart — Next.js

3 min read·Updated Jun 19, 2026

cURL · Next.js · React · Hono · .NET

This guide gets a Next.js (App Router) app authenticating users through Authaz Sign-In. You'll use @authaz/next on the server and @authaz/react for hooks in client components. About 10 minutes.

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 with http://localhost:3000/auth/callback registered as a redirect URI. New to Authaz? Set up your app in 60 seconds — you'll come back with the three env vars below.

Step 1 — Install

Previous
Application Settings
Next
Quickstart — React SPA
#
npm install @authaz/next @authaz/react
# pnpm: pnpm add @authaz/next @authaz/react
# yarn: yarn add @authaz/next @authaz/react
# bun:  bun add @authaz/next @authaz/react

Step 2 — Environment variables#

.env.local:

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: "/",
});

This exposes:

RouteWhat it does
GET /api/auth/loginRedirects to Authaz Sign-In 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.

Step 6 — Use the hooks#

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>
  );
};

Step 7 — Protect a page#

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" });
});

Step 9 — Run it#

npm run dev

Hit http://localhost:3000, click Login, sign up at Authaz Sign-In. You should land on /dashboard and see your email render via the AuthButton. Inside any client component, useAuthaz() now returns:

{
  isAuthenticated: true,
  isLoading: false,
  user: {
    id: "user_01h...",
    email: "you@example.com",
    name: "Your Name",
    mfaEnabled: false,
    organizations: [{ organizationId: "0199...", accessLevel: "owner" }],
  },
  // login, logout, refresh callbacks
}

If you see that user object, auth is working end-to-end.

If something's wrong#

SymptomLikely causeFix
Login click → invalid_request error pageRedirect URI mismatch.Confirm http://localhost:3000/auth/callback is registered exactly in Settings → Redirect URIs.
Land on /auth/callback and see a 404 or blank pageStep 4 callback page missing or has a JS error.Confirm app/auth/callback/page.tsx exists and the form re-POSTs to /api/auth/callback.
useAuthaz() is stuck on isLoading: trueAuthazProvider not wrapping the tree, or basePath doesn't match the handler route.Check app/layout.tsx wraps {children}, and basePath="/api/auth" matches the catch-all in Step 3.
Server logs invalid_client on token exchangeAUTHAZ_CLIENT_SECRET wrong, or you swapped Application ID and Client Secret.Re-copy from Settings → API Keys.
401 from /api/auth/me after a long idleAccess token expired and refresh failed.Confirm autoRefresh is on and that the refresh token cookie is present.

For everything else, the errors catalog covers every code Authaz returns.

Going further#

  • 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.

Complete worked example#

  • Next.js — first integration (single-tenant) — the full app, end to end.
  • Next.js — B2B SaaS (multi-tenant) — same, with tenant pickers and tenant-scoped tokens.

Next steps#

  • React quickstart — same hooks, no Next.js.
  • Multi-tenancy — tenant pickers and tenant-scoped tokens.
  • API Reference — calling the Management API from your Next.js backend.