--- 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} Avatar
); } ``` ### 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