81172bda94
Update all references across source files, documentation, and configuration to reflect the new package scope and name. This includes updating `.npmrc` registry config, install instructions, module examples, and all import path comments throughout the codebase.
348 lines
9.4 KiB
Markdown
348 lines
9.4 KiB
Markdown
# Custom auth pages
|
||
|
||
This guide explains how to build your own auth pages (login, register, forgot password, reset password, confirm email, logout) so they match your site’s layout and style. For a basic site you can keep using the [default auth page](#default-auth-page).
|
||
|
||
## Overview
|
||
|
||
You can use a **custom page for every auth flow**:
|
||
|
||
| Page | Component | Server action(s) |
|
||
|-----------------|-----------------------|-------------------------------------|
|
||
| Login | `LoginPage` | `loginAction`, `setSessionCookie` |
|
||
| Register | `RegisterPage` | `registerAction` |
|
||
| Forgot password | `ForgotPasswordPage` | `forgotPasswordAction` |
|
||
| Reset password | `ResetPasswordPage` | `resetPasswordAction` |
|
||
| Confirm email | `ConfirmEmailPage` | `verifyEmailAction` |
|
||
| Logout | `LogoutPage` | `logoutAction`, `setSessionCookie` |
|
||
|
||
- **Components**: from `@zen/core/auth/components`
|
||
- **Actions**: from `@zen/core/auth/actions`
|
||
|
||
Create your own routes (e.g. `/login`, `/register`, `/auth/forgot`) and wrap Zen’s components in your layout. Each page follows the same pattern: a **server component** that loads data and passes actions, and a **client wrapper** that handles navigation and renders the Zen component.
|
||
|
||
---
|
||
|
||
## Route structure
|
||
|
||
Choose a URL scheme and use it consistently. Two common options:
|
||
|
||
**Option A – All under `/auth/*` (like the default)**
|
||
`/auth/login`, `/auth/register`, `/auth/forgot`, `/auth/reset`, `/auth/confirm`, `/auth/logout`
|
||
|
||
**Option B – Top-level routes**
|
||
`/login`, `/register`, `/forgot`, `/reset`, `/confirm`, `/logout`
|
||
|
||
The `onNavigate` callback receives one of: `'login' | 'register' | 'forgot' | 'reset'`. Map each to your chosen path, e.g. `router.push(\`/auth/${page}\`)` or `router.push(\`/${page}\`)`.
|
||
|
||
Reset and confirm pages need `email` and `token` from the URL (e.g. `/auth/reset?email=...&token=...`). Your server page can read `searchParams` and pass them to the component.
|
||
|
||
---
|
||
|
||
## Component reference (props)
|
||
|
||
Use this when wiring each custom page.
|
||
|
||
| Component | Props |
|
||
|-----------------------|--------|
|
||
| **LoginPage** | `onSubmit` (loginAction), `onSetSessionCookie`, `onNavigate`, `redirectAfterLogin`, `currentUser` |
|
||
| **RegisterPage** | `onSubmit` (registerAction), `onNavigate`, `currentUser` |
|
||
| **ForgotPasswordPage**| `onSubmit` (forgotPasswordAction), `onNavigate`, `currentUser` |
|
||
| **ResetPasswordPage** | `onSubmit` (resetPasswordAction), `onNavigate`, `email`, `token` (from URL) |
|
||
| **ConfirmEmailPage** | `onSubmit` (verifyEmailAction), `onNavigate`, `email`, `token` (from URL) |
|
||
| **LogoutPage** | `onLogout` (logoutAction), `onSetSessionCookie` (optional) |
|
||
|
||
---
|
||
|
||
## 1. Login
|
||
|
||
**Server:** `app/login/page.js` (or `app/auth/login/page.js`)
|
||
|
||
```js
|
||
import { getSession, loginAction, setSessionCookie } from '@zen/core/auth/actions';
|
||
import { LoginPageWrapper } from './LoginPageWrapper';
|
||
|
||
export default async function LoginRoute() {
|
||
const session = await getSession();
|
||
return (
|
||
<YourSiteLayout>
|
||
<LoginPageWrapper
|
||
loginAction={loginAction}
|
||
setSessionCookie={setSessionCookie}
|
||
currentUser={session?.user ?? null}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/login/LoginPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
import { LoginPage } from '@zen/core/auth/components';
|
||
|
||
export function LoginPageWrapper({ loginAction, setSessionCookie, currentUser }) {
|
||
const router = useRouter();
|
||
return (
|
||
<LoginPage
|
||
onSubmit={loginAction}
|
||
onSetSessionCookie={setSessionCookie}
|
||
onNavigate={(page) => router.push(`/auth/${page}`)}
|
||
redirectAfterLogin="/"
|
||
currentUser={currentUser}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Register
|
||
|
||
**Server:** `app/register/page.js`
|
||
|
||
```js
|
||
import { getSession, registerAction } from '@zen/core/auth/actions';
|
||
import { RegisterPageWrapper } from './RegisterPageWrapper';
|
||
|
||
export default async function RegisterRoute() {
|
||
const session = await getSession();
|
||
return (
|
||
<YourSiteLayout>
|
||
<RegisterPageWrapper
|
||
registerAction={registerAction}
|
||
currentUser={session?.user ?? null}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/register/RegisterPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
import { RegisterPage } from '@zen/core/auth/components';
|
||
|
||
export function RegisterPageWrapper({ registerAction, currentUser }) {
|
||
const router = useRouter();
|
||
return (
|
||
<RegisterPage
|
||
onSubmit={registerAction}
|
||
onNavigate={(page) => router.push(`/auth/${page}`)}
|
||
currentUser={currentUser}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Forgot password
|
||
|
||
**Server:** `app/forgot/page.js`
|
||
|
||
```js
|
||
import { getSession, forgotPasswordAction } from '@zen/core/auth/actions';
|
||
import { ForgotPasswordPageWrapper } from './ForgotPasswordPageWrapper';
|
||
|
||
export default async function ForgotRoute() {
|
||
const session = await getSession();
|
||
return (
|
||
<YourSiteLayout>
|
||
<ForgotPasswordPageWrapper
|
||
forgotPasswordAction={forgotPasswordAction}
|
||
currentUser={session?.user ?? null}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/forgot/ForgotPasswordPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
import { ForgotPasswordPage } from '@zen/core/auth/components';
|
||
|
||
export function ForgotPasswordPageWrapper({ forgotPasswordAction, currentUser }) {
|
||
const router = useRouter();
|
||
return (
|
||
<ForgotPasswordPage
|
||
onSubmit={forgotPasswordAction}
|
||
onNavigate={(page) => router.push(`/auth/${page}`)}
|
||
currentUser={currentUser}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Reset password
|
||
|
||
Requires `email` and `token` from the reset link (e.g. `/auth/reset?email=...&token=...`). Read `searchParams` in the server component and pass them to the client.
|
||
|
||
**Server:** `app/auth/reset/page.js` (or `app/reset/page.js` with dynamic segment if needed)
|
||
|
||
```js
|
||
import { resetPasswordAction } from '@zen/core/auth/actions';
|
||
import { ResetPasswordPageWrapper } from './ResetPasswordPageWrapper';
|
||
|
||
export default async function ResetRoute({ searchParams }) {
|
||
const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {};
|
||
const email = params.email ?? '';
|
||
const token = params.token ?? '';
|
||
return (
|
||
<YourSiteLayout>
|
||
<ResetPasswordPageWrapper
|
||
resetPasswordAction={resetPasswordAction}
|
||
email={email}
|
||
token={token}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/auth/reset/ResetPasswordPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
import { ResetPasswordPage } from '@zen/core/auth/components';
|
||
|
||
export function ResetPasswordPageWrapper({ resetPasswordAction, email, token }) {
|
||
const router = useRouter();
|
||
return (
|
||
<ResetPasswordPage
|
||
onSubmit={resetPasswordAction}
|
||
onNavigate={(page) => router.push(`/auth/${page}`)}
|
||
email={email}
|
||
token={token}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Confirm email
|
||
|
||
Requires `email` and `token` from the verification link (e.g. `/auth/confirm?email=...&token=...`).
|
||
|
||
**Server:** `app/auth/confirm/page.js`
|
||
|
||
```js
|
||
import { verifyEmailAction } from '@zen/core/auth/actions';
|
||
import { ConfirmEmailPageWrapper } from './ConfirmEmailPageWrapper';
|
||
|
||
export default async function ConfirmRoute({ searchParams }) {
|
||
const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {};
|
||
const email = params.email ?? '';
|
||
const token = params.token ?? '';
|
||
return (
|
||
<YourSiteLayout>
|
||
<ConfirmEmailPageWrapper
|
||
verifyEmailAction={verifyEmailAction}
|
||
email={email}
|
||
token={token}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/auth/confirm/ConfirmEmailPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
import { ConfirmEmailPage } from '@zen/core/auth/components';
|
||
|
||
export function ConfirmEmailPageWrapper({ verifyEmailAction, email, token }) {
|
||
const router = useRouter();
|
||
return (
|
||
<ConfirmEmailPage
|
||
onSubmit={verifyEmailAction}
|
||
onNavigate={(page) => router.push(`/auth/${page}`)}
|
||
email={email}
|
||
token={token}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Logout
|
||
|
||
**Server:** `app/auth/logout/page.js`
|
||
|
||
```js
|
||
import { logoutAction, setSessionCookie } from '@zen/core/auth/actions';
|
||
import { LogoutPageWrapper } from './LogoutPageWrapper';
|
||
|
||
export default function LogoutRoute() {
|
||
return (
|
||
<YourSiteLayout>
|
||
<LogoutPageWrapper
|
||
logoutAction={logoutAction}
|
||
setSessionCookie={setSessionCookie}
|
||
/>
|
||
</YourSiteLayout>
|
||
);
|
||
}
|
||
```
|
||
|
||
**Client:** `app/auth/logout/LogoutPageWrapper.js`
|
||
|
||
```js
|
||
'use client';
|
||
|
||
import { LogoutPage } from '@zen/core/auth/components';
|
||
|
||
export function LogoutPageWrapper({ logoutAction, setSessionCookie }) {
|
||
return (
|
||
<LogoutPage
|
||
onLogout={logoutAction}
|
||
onSetSessionCookie={setSessionCookie}
|
||
/>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Protecting routes
|
||
|
||
Use `protect()` from `@zen/core/auth` and set `redirectTo` to your custom login path:
|
||
|
||
```js
|
||
import { protect } from '@zen/core/auth';
|
||
|
||
export const middleware = protect({ redirectTo: '/login' });
|
||
```
|
||
|
||
So unauthenticated users are sent to your custom login page.
|
||
|
||
---
|
||
|
||
## Default auth page
|
||
|
||
If you don’t need a custom layout, keep using the built-in auth UI. In `app/auth/[...auth]/page.js`:
|
||
|
||
```js
|
||
export { default } from '@zen/core/auth/page';
|
||
```
|
||
|
||
This serves login, register, forgot, reset, confirm, and logout under `/auth/*` with the default styling.
|