---
title: Next.js — first integration
project: Authaz
updated: 2026-05-07T23:22:34.179Z
---

# Next.js — first integration

> [← All recipes](./index.md) · First time? [Set up your app](../quickstart/setup.md) (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).

…or via the Management API:

```http
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

```bash
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`:

```typescript
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`:

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

```typescript
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`:

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

```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](./nextjs-multi-tenant.md) — same app shape with tenants and tenant-scoped roles
- [Authentication providers](../authentication/settings.md) — add Google, magic link, passkey
- [Security policies](../authentication/settings.md) — password rules, MFA enforcement, session timeouts
