---
status: "published"
label: "Authentication API"
order: 2
title: "Authentication API | Next.js Supabase SaaS Kit"
description: "Complete reference for authentication in MakerKit. Use requireUser for server-side auth checks, handle MFA verification, and access user data in client components."
---
The Authentication API verifies user identity, handles MFA (Multi-Factor Authentication), and provides user data to your components. Use `requireUser` on the server for protected routes and `useUser` on the client for reactive user state.
{% sequence title="Authentication API Reference" description="Learn how to authenticate users in MakerKit" %}
[requireUser (Server)](#requireuser-server)
[useUser (Client)](#useuser-client)
[useSupabase (Client)](#usesupabase-client)
[MFA handling](#mfa-handling)
[Common patterns](#common-patterns)
{% /sequence %}
## requireUser (Server)
The `requireUser` function checks authentication status in Server Components, Server Actions, and Route Handlers. It handles both standard auth and MFA verification in a single call.
```tsx
import { redirect } from 'next/navigation';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
async function ProtectedPage() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
const user = auth.data;
return
Welcome, {user.email}
;
}
```
### Function signature
```tsx
function requireUser(
client: SupabaseClient,
options?: {
verifyMfa?: boolean; // Default: true
}
): Promise
```
### Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `client` | `SupabaseClient` | required | Supabase server client |
| `options.verifyMfa` | `boolean` | `true` | Check MFA status |
### Response types
**Success response:**
```tsx
{
data: {
id: string; // User UUID
email: string; // User email
phone: string; // User phone (if set)
is_anonymous: boolean; // Anonymous auth flag
aal: 'aal1' | 'aal2'; // Auth Assurance Level
app_metadata: Record;
user_metadata: Record;
amr: AMREntry[]; // Auth Methods Reference
};
error: null;
}
```
**Error response:**
```tsx
{
data: null;
error: AuthenticationError | MultiFactorAuthError;
redirectTo: string; // Where to redirect the user
}
```
### Auth Assurance Levels (AAL)
| Level | Meaning |
|-------|---------|
| `aal1` | Basic authentication (password, magic link, OAuth) |
| `aal2` | MFA verified (TOTP app, etc.) |
### Error types
| Error | Cause | Redirect |
|-------|-------|----------|
| `AuthenticationError` | User not logged in | Sign-in page |
| `MultiFactorAuthError` | MFA required but not verified | MFA verification page |
### Usage in Server Components
```tsx
import { redirect } from 'next/navigation';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
export default async function DashboardPage() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
return (
Dashboard
Logged in as: {auth.data.email}
MFA status: {auth.data.aal === 'aal2' ? 'Verified' : 'Not verified'}
);
}
```
### Usage in Server Actions
```tsx
'use server';
import { redirect } from 'next/navigation';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
export async function updateProfile(formData: FormData) {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
const name = formData.get('name') as string;
await client
.from('profiles')
.update({ name })
.eq('id', auth.data.id);
return { success: true };
}
```
### Skipping MFA verification
For pages that don't require full MFA verification:
```tsx
const auth = await requireUser(client, { verifyMfa: false });
```
{% callout type="warning" title="MFA security" %}
Only disable MFA verification for non-sensitive pages. Always verify MFA for billing, account deletion, and other high-risk operations.
{% /callout %}
---
## useUser (Client)
The `useUser` hook provides reactive access to user data in client components. It reads from the auth context and updates automatically on auth state changes.
```tsx
'use client';
import { useUser } from '@kit/supabase/hooks/use-user';
function UserMenu() {
const user = useUser();
if (!user) {
return Loading...
;
}
return (
{user.email}
);
}
```
### Return type
```tsx
User | null
```
The `User` type from Supabase includes:
```tsx
{
id: string;
email: string;
phone: string;
created_at: string;
updated_at: string;
app_metadata: {
provider: string;
providers: string[];
};
user_metadata: {
avatar_url?: string;
full_name?: string;
// Custom metadata fields
};
aal?: 'aal1' | 'aal2';
}
```
### Conditional rendering
```tsx
'use client';
import { useUser } from '@kit/supabase/hooks/use-user';
function ConditionalContent() {
const user = useUser();
// Show loading state
if (user === undefined) {
return ;
}
// Not authenticated
if (!user) {
return ;
}
// Authenticated
return ;
}
```
---
## useSupabase (Client)
The `useSupabase` hook provides the Supabase browser client for client-side operations.
```tsx
'use client';
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { useQuery } from '@tanstack/react-query';
function TaskList() {
const supabase = useSupabase();
const { data: tasks } = useQuery({
queryKey: ['tasks'],
queryFn: async () => {
const { data, error } = await supabase
.from('tasks')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return data;
},
});
return (
{tasks?.map((task) => (
- {task.title}
))}
);
}
```
---
## MFA handling
MakerKit automatically handles MFA verification through the `requireUser` function.
### How it works
1. User logs in with password/OAuth (reaches `aal1`)
2. If MFA is enabled, `requireUser` checks AAL
3. If `aal1` but MFA required, redirects to MFA verification
4. After TOTP verification, user reaches `aal2`
5. Protected pages now accessible
### MFA flow diagram
```
Login → aal1 → requireUser() → MFA enabled?
↓
Yes: redirect to /auth/verify
↓
User enters TOTP
↓
aal2 → Access granted
```
### Checking MFA status
```tsx
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
async function checkMfaStatus() {
const client = getSupabaseServerClient();
const auth = await requireUser(client, { verifyMfa: false });
if (auth.error) {
return { authenticated: false };
}
return {
authenticated: true,
mfaEnabled: auth.data.aal === 'aal2',
authMethods: auth.data.amr.map((m) => m.method),
};
}
```
---
## Common patterns
### Protected API Route Handler
```tsx
// app/api/user/route.ts
import { NextResponse } from 'next/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
export async function GET() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const { data: profile } = await client
.from('profiles')
.select('*')
.eq('id', auth.data.id)
.single();
return NextResponse.json({ user: auth.data, profile });
}
```
### Using authActionClient (recommended)
The `authActionClient` utility handles authentication automatically:
```tsx
'use server';
import * as z from 'zod';
import { authActionClient } from '@kit/next/safe-action';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
const UpdateProfileSchema = z.object({
name: z.string().min(2),
});
export const updateProfile = authActionClient
.inputSchema(UpdateProfileSchema)
.action(async ({ parsedInput: data, ctx: { user } }) => {
// user is automatically available and typed
const client = getSupabaseServerClient();
await client
.from('profiles')
.update({ name: data.name })
.eq('id', user.id);
return { success: true };
});
```
### Public actions (no auth)
```tsx
import { publicActionClient } from '@kit/next/safe-action';
export const submitContactForm = publicActionClient
.inputSchema(ContactFormSchema)
.action(async ({ parsedInput: data }) => {
// No user context in public actions
await sendEmail(data);
return { success: true };
});
```
### Role-based access control
Combine authentication with role checks:
```tsx
import { redirect } from 'next/navigation';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { isSuperAdmin } from '@kit/admin';
async function AdminPage() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
const isAdmin = await isSuperAdmin(client);
if (!isAdmin) {
redirect('/home');
}
return ;
}
```
### Auth state listener (Client)
For real-time auth state changes:
```tsx
'use client';
import { useEffect } from 'react';
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
function AuthStateListener({ onAuthChange }) {
const supabase = useSupabase();
useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
onAuthChange({ type: 'signed_in', user: session?.user });
} else if (event === 'SIGNED_OUT') {
onAuthChange({ type: 'signed_out' });
} else if (event === 'TOKEN_REFRESHED') {
onAuthChange({ type: 'token_refreshed' });
}
});
return () => subscription.unsubscribe();
}, [supabase, onAuthChange]);
return null;
}
```
## Common mistakes
### Creating client at module scope
```tsx
// WRONG: Client created at module scope
const client = getSupabaseServerClient();
export async function handler() {
const auth = await requireUser(client); // Won't work
}
// RIGHT: Client created in request context
export async function handler() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
}
```
### Ignoring the redirectTo property
```tsx
// WRONG: Not using redirectTo
if (auth.error) {
redirect('/login'); // MFA users sent to wrong page
}
// RIGHT: Use the provided redirectTo
if (auth.error) {
redirect(auth.redirectTo); // Correct handling for auth + MFA
}
```
### Using useUser for server-side checks
```tsx
// WRONG: useUser is client-only
export async function ServerComponent() {
const user = useUser(); // Won't work
}
// RIGHT: Use requireUser on server
export async function ServerComponent() {
const client = getSupabaseServerClient();
const auth = await requireUser(client);
}
```
## Related documentation
- [Account API](/docs/next-supabase-turbo/api/account-api) - Personal account operations
- [Server Actions](/docs/next-supabase-turbo/data-fetching/server-actions) - Using authActionClient
- [Route Handlers](/docs/next-supabase-turbo/data-fetching/route-handlers) - API authentication