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. Recipes
  4. Next.js — first integration

Recipes

Next.js — first integration

2 min read·Updated Jun 19, 2026

← All recipes · First time? Set up your app (60 seconds — keys + redirect URI)

A hello-world Next.js (App Router) app that signs a user in via Authaz Sign-In, lands them on a protected page, and lets them log out. Single-tenant, password provider, no roles.

1. What you'll build#

A Next.js 15 app where / is public, /dashboard is protected, and the user's email and ID come from Authaz over OAuth 2.1 + PKCE.

2. Application setup#

In the Authaz Dashboard:

  1. New application → name it, choose Single-tenant.
  2. Note the Client ID and Client Secret.
  3. Under Auth Flow Configuration, add http://localhost:3000/auth/callback as an allowed callback URL.
  4. Authentication → Password → leave defaults (auto-enabled).
Previous
Recipes & Cookbook
Next
Next.js — B2B SaaS (multi-tenant)

…or via the Management API:

POST https://your-app.authaz.io/api/v1/applications
X-API-Key: mgmt_01h...
Content-Type: application/json
 
{ "name": "my-nextjs-app", "tenancy_type": "single_tenant" }

3. Install#

pnpm add @authaz/next

4. Configure#

.env.local:

AUTHAZ_CLIENT_ID=app_01h...
AUTHAZ_CLIENT_SECRET=secret_...
AUTHAZ_ORGANIZATION_ID=0199...
AUTHAZ_API_KEY=mgmt_01h...

5. Wire it up#

Mount the auth handler — 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!,
  afterLoginUrl: "/dashboard",
  afterLogoutUrl: "/",
});

This exposes /api/auth/login, /api/auth/callback (POST), /api/auth/logout, /api/auth/me, /api/auth/refresh.

OAuth callback page — Authaz redirects back via GET, but the handler expects a POST. Use a tiny client component to bridge them. app/auth/callback/page.tsx:

"use client";
import { useEffect } from "react";
import { useSearchParams } from "next/navigation";
 
export default function Callback() {
  const params = useSearchParams();
  useEffect(() => {
    const form = document.createElement("form");
    form.method = "POST";
    form.action = "/api/auth/callback";
    for (const k of ["code", "state", "error", "error_description"]) {
      const v = params.get(k);
      if (v) {
        const i = document.createElement("input");
        i.type = "hidden"; i.name = k; i.value = v;
        form.appendChild(i);
      }
    }
    document.body.appendChild(form);
    form.submit();
  }, [params]);
  return <p>Completing sign-in…</p>;
}

Protect routes via middleware — middleware.ts:

import { createAuthMiddleware } from "@authaz/next";
 
export default createAuthMiddleware({
  publicPaths: ["/", "/api/auth/*", "/auth/callback"],
  loginPath: "/api/auth/login",
});
 
export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

6. Read the user#

Use requireUser in any server component. app/dashboard/page.tsx:

import { requireUser } from "@authaz/next";
 
const user = requireUser({
  organizationId: process.env.AUTHAZ_ORGANIZATION_ID!,
  apiKey: process.env.AUTHAZ_API_KEY!,
});
 
export default async function Dashboard() {
  const me = await user.getOrRedirect();
  return (
    <main>
      <h1>Hello, {me.email}</h1>
      <p>Your Authaz user id: <code>{me.id}</code></p>
      <form action="/api/auth/logout" method="POST">
        <button type="submit">Sign out</button>
      </form>
    </main>
  );
}

7. Login button#

app/page.tsx:

export default function Home() {
  return (
    <main>
      <h1>Welcome</h1>
      <a href="/api/auth/login">Sign in</a>
    </main>
  );
}

That's the whole integration. pnpm dev, click Sign in, complete the password flow, you land on /dashboard signed in.

8. Common gotchas#

  • Callback URL must match exactly. A trailing slash, http vs https, or a different port all fail. Register every dev/staging/prod URL.
  • /auth/callback is a page, not an API route. Authaz redirects to a page that POSTs the code into the SDK handler. Don't try to handle the OAuth callback at /api/auth/callback directly.
  • Cookies need HTTPS in production. The SDK auto-detects HTTPS; behind a load balancer set x-forwarded-proto: https.
  • Logout is POST, not GET. Use a <form method="POST"> button, not a link.
  • Don't put the client secret in client components. It's a server-side env var only — never NEXT_PUBLIC_*.

9. Next steps#

  • Multi-tenant Next.js recipe — same app shape with tenants and tenant-scoped roles
  • Authentication providers — add Google, magic link, passkey
  • Security policies — password rules, MFA enforcement, session timeouts