Supabase TOTP 2FA for Next.js App Router
By (author)fizzah abdullah
Tech stack:
JS
TS
CSS
Drop-in 2FA for Next.js and Supabase: authenticator setup with a QR code, a code check at sign-in, and a guard that blocks pages, API routes and data...
read more
read more
AI Contributions:0
Published At:October 19, 2026
Files Included:5 file(s)
Read Me
# Supabase TOTP 2FA for Next.js App Router
## Description
Drop-in two-factor authentication for a Next.js App Router app using Supabase Auth MFA. It includes TOTP enrolment with a QR code and manual secret, a verification screen for every password sign-in, middleware redirects, server component guards, API route guards, and database policies that require an `aal2` session.
## Skills
Next.js, React, TypeScript, Supabase, PostgreSQL
## Custom tags
2FA, TOTP, MFA, authentication, Row Level Security, App Router
## What it does
- Redirects signed-in users with no verified TOTP factor to `/2fa/enrol`.
- Redirects enrolled users whose current session is still `aal1` to `/2fa/verify`.
- Uses `supabase.auth.mfa.enroll`, `challenge`, and `verify` / `challengeAndVerify` flow to promote the session to `aal2`.
- Provides `requireAal2User()` for Server Components and `requireApiAal2Session()` for route handlers.
- Complements Supabase Row Level Security policies that check `(select auth.jwt()->>'aal') = 'aal2'`.
## Files included in this app
- `middleware.ts`
- `lib/supabase/middleware.ts`
- `lib/auth/session.ts`
- `lib/auth/redirect.ts`
- `app/(auth)/2fa/enrol/page.tsx`
- `app/(auth)/2fa/verify/page.tsx`
- `components/auth/two-factor-form.tsx`
- `supabase/migrations/20250101000000_core_tables.sql`
- `supabase/migrations/20250101000100_profile_trigger.sql`
## Install
```bash
npm install next react react-dom @supabase/ssr @supabase/supabase-js
```
## Setup
1. Create a Supabase project and enable Email auth.
2. In Supabase Auth, enable MFA with TOTP factors.
3. Add these variables to your environment:
```bash
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
```
4. Use `@supabase/ssr` clients so the browser, route handlers and middleware share the same cookie-backed session.
5. Add RLS policies that require both the signed-in user and an AAL2 session:
```sql
using (
user_id = (select auth.uid())
and (select auth.jwt()->>'aal') = 'aal2'
)
```
## Usage example
Protect pages in middleware before they render:
```ts
if (user && isProtectedPath(pathname)) {
if (aal.nextLevel === "aal1") {
const enrol = new URL("/2fa/enrol", origin);
enrol.searchParams.set("next", `${pathname}${search}`);
return redirectWithCookies(enrol, response);
}
if (aal.nextLevel === "aal2" && aal.currentLevel !== "aal2") {
const verify = new URL("/2fa/verify", origin);
verify.searchParams.set("next", `${pathname}${search}`);
return redirectWithCookies(verify, response);
}
}
```
Protect Server Components too:
```ts
import { requireAal2User } from "@/lib/auth/session";
export default async function DashboardPage() {
const user = await requireAal2User("/dashboard");
return <p>Signed in as {user.email}</p>;
}
```
Protect route handlers:
```ts
const session = await requireApiAal2Session();
if (!session.ok) {
return Response.json({ ok: false, error: session.reason }, { status: session.status });
}
```
## Options and limits
- The sample protects `/dashboard`; add more protected prefixes in `lib/auth/redirect.ts`.
- `next` redirects are sanitized to in-app paths only.
- TOTP codes are generated by the user's authenticator app and usually rotate every 30 seconds.
- Supabase determines `currentLevel` and `nextLevel`; the app should not infer 2FA state from local storage or user metadata.
- API guards return JSON errors instead of redirects so clients can show a friendly message.
## Dependencies and licences
- Next.js — MIT: https://github.com/vercel/next.js
- React — MIT: https://github.com/facebook/react
- Supabase JS — MIT: https://github.com/supabase/supabase-js
- Supabase SSR — MIT: https://github.com/supabase/ssr
- PostgreSQL / Supabase Row Level Security — PostgreSQL licence / Supabase platform docs
## Contributors
Roshaan Tahir, M Hammad Khan, Fizzah Abdullah, Hammad Khan
---
## GIG READY block
GIG READY: Supabase TOTP 2FA
Publish at Devmarketplace → Add your Gigs
1. Thumbnail
Upload docs/gigs/thumbnails/supabase-totp-2fa.png
2. Basic Info
Title (40/50): Supabase TOTP 2FA for Next.js App Router
Description (177/200): Drop-in 2FA for Next.js and Supabase: authenticator setup with a QR code, a code check at sign-in, and a guard that blocks pages, API routes and database rows until 2FA is done.
3. Tech Stack
Skills (pick the closest ones in the list): Next.js, React, TypeScript, Supabase, PostgreSQL
Custom tags (6/10, press Enter after each): 2FA, TOTP, MFA, authentication, Row Level Security, App Router
4. Code Snippet
Language: TypeScript
From: middleware.ts, 27 to 43
```ts
if (user && isProtectedPath(pathname)) {
if (aal.nextLevel === "aal1") {
const enrol = new URL(TWO_FACTOR_ENROL_PATH, origin);
enrol.searchParams.set("next", `${pathname}${search}`);
return redirectWithCookies(enrol, response);
}
if (aal.nextLevel === "aal2" && aal.currentLevel !== "aal2") {
const verify = new URL(TWO_FACTOR_VERIFY_PATH, origin);
verify.searchParams.set("next", `${pathname}${search}`);
return redirectWithCookies(verify, response);
}
}
```
5. Read ME
Open docs/gigs/supabase-totp-2fa.md on GitHub, copy the rendered page and paste it into the editor.
6. After you click Create
Add the gig link to the tracker in docs/GIGS.md and to the Devpost draft.