---
# === IDENTITY ===
id: software/mvp-development/authentication-authorization-setup/2026
canonical_question: "How do I set up auth — Auth0, Clerk, Supabase Auth, Firebase Auth with social login and magic links?"
aliases:
  - "Best authentication provider for MVP startups"
  - "Clerk vs Auth0 vs Supabase Auth comparison for Next.js"
  - "How to add social login and magic links to my app"
  - "Authentication setup for SaaS MVP with OAuth and email"
  - "Firebase Auth vs Supabase Auth for startups 2026"
entity_type: execution_recipe
domain: software > mvp-development > authentication & authorization setup
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === VERIFICATION ===
last_verified: 2026-03-12
confidence: 0.91
version: 1.0
first_published: 2026-03-12

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Clerk raised free tier to 10,000 MAU in 2025; Supabase migrated auth helpers to @supabase/ssr package; Auth0 rebranded pricing under Okta"
  next_review: 2026-09-08
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "OAuth redirect URIs must match exactly between provider dashboard and application — trailing slash mismatches cause silent failures"
  - "Magic link emails may land in spam folders — configure custom SMTP (Resend, SendGrid) for production"
  - "Clerk free tier: 10,000 MAU; Supabase free tier: 50,000 MAU; Firebase free tier: 50,000 MAU; Auth0 free tier: 25,000 MAU"
  - "Social login requires OAuth app registration with each provider (Google, GitHub, etc.) — use platform-provided test credentials for development only"
  - "Store session tokens in httpOnly cookies, not localStorage — localStorage is vulnerable to XSS attacks"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User needs to choose a tech stack, not implement auth"
    use_instead: "Search knowledgelib.io for MVP tech stack selection — no dedicated unit yet"
  - condition: "User needs enterprise SSO/SAML specifically"
    use_instead: "software/patterns/sso-saml-oidc/2026"
  - condition: "User needs a complete project scaffold, not just auth"
    use_instead: "software/mvp-development/coded-mvp-architecture-templates/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: auth_provider
    question: "Which authentication provider should be used?"
    type: choice
    options: ["Clerk", "Supabase Auth", "Firebase Auth", "Auth0", "no preference — auto-select"]
  - key: auth_methods
    question: "Which authentication methods are needed?"
    type: choice
    options: ["email + password only", "email + password + social login", "magic link + social login", "all methods (email, magic link, social, phone)"]
  - key: framework
    question: "Which frontend framework?"
    type: choice
    options: ["Next.js (App Router)", "Next.js (Pages Router)", "React (Vite)", "React Native", "other"]
  - key: authorization_needs
    question: "What authorization level is needed?"
    type: choice
    options: ["simple (authenticated vs unauthenticated)", "role-based (admin, member, viewer)", "organization-based (multi-tenant)", "attribute-based (fine-grained permissions)"]

# === EXECUTION METADATA ===
execution:
  required_inputs:
    - name: "Framework and project scaffold"
      source: "software/mvp-development/coded-mvp-architecture-templates/2026"
      format: "existing Next.js/React project"
    - name: "Authentication requirements"
      source: "product requirements"
      format: "list of auth methods and user roles needed"
  outputs:
    - name: "Working authentication system"
      format: "code + configured provider"
      description: "Complete auth flow: signup, login (email, social, magic link), logout, session management, protected routes"
    - name: "Authorization middleware"
      format: "code"
      description: "Role-based or organization-based access control on routes and API endpoints"
  tools_required:
    - name: "Clerk"
      purpose: "Drop-in auth UI components with session management"
      tier: "free"
      cost: "$0 (10K MAU) then $0.02/MAU"
      alternatives: ["Supabase Auth", "Auth0", "Firebase Auth"]
    - name: "Supabase Auth"
      purpose: "Auth bundled with PostgreSQL database and RLS"
      tier: "free"
      cost: "$0 (50K MAU) then $0.00325/MAU"
      alternatives: ["Clerk", "Auth0"]
    - name: "Auth0"
      purpose: "Enterprise-grade auth with SAML SSO and compliance"
      tier: "free"
      cost: "$0 (25K MAU) then $0.07/MAU"
      alternatives: ["Clerk", "Supabase Auth"]
    - name: "Firebase Auth"
      purpose: "Google ecosystem auth with phone verification"
      tier: "free"
      cost: "$0 (50K MAU) then SMS-based pricing"
      alternatives: ["Supabase Auth", "Clerk"]
  credentials_needed:
    - service: "Auth provider account"
      type: "API keys (publishable + secret)"
      where_to_get: "Provider dashboard (clerk.com, supabase.com, auth0.com, firebase.google.com)"
      free_tier_limits: "Varies: 10K-50K MAU"
    - service: "Google OAuth (for social login)"
      type: "Client ID + Client Secret"
      where_to_get: "https://console.cloud.google.com/apis/credentials"
      free_tier_limits: "Unlimited"
    - service: "GitHub OAuth (for social login)"
      type: "Client ID + Client Secret"
      where_to_get: "https://github.com/settings/developers"
      free_tier_limits: "Unlimited"
  estimated_duration: "30-60 minutes for complete auth setup"
  estimated_cost: "$0 (all providers have generous free tiers)"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/mvp-development/authentication-authorization-setup/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-12)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/mvp-development/coded-mvp-architecture-templates/2026"
      label: "Project scaffold before auth setup"
  feeds_into:
    - id: "software/mvp-development/payment-integration-setup/2026"
      label: "Payment integration requires authenticated users"
    - id: "software/mvp-development/database-schema-design-for-mvps/2026"
      label: "User profiles and auth tables"
  related_to:
    - id: "software/mvp-development/ai-assisted-code-generation-workflow/2026"
      label: "Use AI to generate auth code"
  alternative_to: []

# === SOURCES ===
sources:
  - id: src1
    title: "Use Supabase Auth with Next.js"
    author: Supabase
    url: https://supabase.com/docs/guides/auth/quickstarts/nextjs
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src2
    title: "SignIn Component — Clerk Docs for Next.js"
    author: Clerk
    url: https://clerk.com/docs/nextjs/reference/components/authentication/sign-in
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src3
    title: "Auth Pricing Wars: Cognito vs Auth0 vs Firebase vs Supabase"
    author: Zuplo
    url: https://zuplo.com/learning-center/api-authentication-pricing
    type: technical_blog
    published: 2025-06-01
    reliability: high
  - id: src4
    title: "User Management Platform Comparison: Clerk vs Auth0 vs Firebase"
    author: Clerk
    url: https://clerk.com/articles/user-management-platform-comparison-react-clerk-auth0-firebase
    type: technical_blog
    published: 2025-03-01
    reliability: high
  - id: src5
    title: "Passwordless Email Logins — Supabase Docs"
    author: Supabase
    url: https://supabase.com/docs/guides/auth/auth-email-passwordless
    type: official_docs
    published: 2025-01-01
    reliability: authoritative
  - id: src6
    title: "Comparing Auth Providers: Supabase, Firebase, Clerk, and Others"
    author: Hyperknot
    url: https://blog.hyperknot.com/p/comparing-auth-providers
    type: technical_blog
    published: 2025-01-01
    reliability: high
  - id: src7
    title: "Clerk vs Supabase Auth: Budget Comparison"
    author: Monetizely
    url: https://www.getmonetizely.com/articles/clerk-vs-supabase-auth-how-to-choose-the-right-authentication-service-for-your-budget
    type: technical_blog
    published: 2025-09-01
    reliability: high
---

# Authentication & Authorization Setup

## Purpose

This recipe implements a complete authentication system for an MVP — covering provider selection, configuration, social login (Google, GitHub), magic links, email/password, protected routes, and role-based authorization. The output is a working auth flow where users can sign up, log in via multiple methods, access protected pages, and be restricted by role — all within 60 minutes. [src1]

## Prerequisites

- [ ] **Project scaffold** — existing Next.js or React project from [Coded MVP Architecture Templates](/software/mvp-development/coded-mvp-architecture-templates/2026)
- [ ] **Auth provider account** — [Clerk](https://clerk.com), [Supabase](https://supabase.com), [Auth0](https://auth0.com), or [Firebase](https://firebase.google.com)
- [ ] **Google OAuth credentials** (for social login) — from [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
- [ ] **GitHub OAuth app** (for social login) — from [GitHub Developer Settings](https://github.com/settings/developers)
- [ ] **Custom SMTP** (recommended for magic links) — [Resend](https://resend.com) ($0 for 3,000 emails/mo) or [SendGrid](https://sendgrid.com)

## Constraints

- OAuth redirect URIs must match exactly — a trailing slash mismatch (`/callback` vs `/callback/`) causes silent authentication failures. [src1]
- Magic link emails may land in spam with default SMTP — configure custom SMTP before user testing. [src5]
- Clerk requires wrapping the app with `<ClerkProvider>` — this must be in the root layout, not a page component. [src2]
- Supabase Auth uses `@supabase/ssr` for cookie-based sessions in Next.js — do not use the deprecated `@supabase/auth-helpers-nextjs`. [src1]
- Auth0 Universal Login requires redirect to Auth0-hosted page — this adds latency but provides the most compliant flow for enterprise use. [src4]

## Tool Selection Decision

```
Which auth provider?
├── Next.js SaaS AND want drop-in UI components AND < 10K users
│   └── PROVIDER A: Clerk — fastest setup, pre-built components, $0 for 10K MAU
├── Already using Supabase for database AND want bundled auth
│   └── PROVIDER B: Supabase Auth — integrated with RLS, $0 for 50K MAU
├── Need enterprise SSO/SAML AND HIPAA compliance
│   └── PROVIDER C: Auth0 — most compliant, $0 for 25K MAU
├── Google ecosystem AND need phone auth
│   └── PROVIDER D: Firebase Auth — phone OTP, $0 for 50K MAU
└── Unsure
    └── DEFAULT: Supabase Auth (if using Supabase DB) or Clerk (if not)
```

| Provider | Free MAU | Cost After | Setup Time | Pre-built UI | Social Login | Magic Link | Phone Auth |
|----------|----------|-----------|------------|-------------|-------------|------------|------------|
| Clerk | 10,000 | $0.02/MAU | 15 min | Yes (excellent) | Yes | Yes | Yes |
| Supabase Auth | 50,000 | $0.00325/MAU | 30 min | No (build your own) | Yes | Yes | Yes |
| Auth0 | 25,000 | $0.07/MAU | 45 min | Redirect-based | Yes | Yes | SMS add-on |
| Firebase Auth | 50,000 | SMS pricing | 30 min | FirebaseUI (basic) | Yes | No (email link) | Yes |

## Execution Flow

### Step 1: Create Provider Account and Get Credentials

**Duration**: 5 minutes
**Tool**: Provider dashboard

**Provider A — Clerk:** [src2]

```bash
# 1. Sign up at clerk.com
# 2. Create new application
# 3. Copy keys to .env.local:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# 4. Enable auth methods in Clerk Dashboard:
#    User & Authentication → Email, Password, Username
#    User & Authentication → Social Connections → Enable Google, GitHub
#    User & Authentication → Web3 → Magic Links (toggle on)
```

**Provider B — Supabase Auth:** [src1]

```bash
# 1. Create project at supabase.com/dashboard
# 2. Go to Settings → API → Copy keys:

NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG...

# 3. Enable providers:
#    Authentication → Providers → Enable Email, Google, GitHub
# 4. For Google: paste Client ID and Secret from Google Cloud Console
# 5. For GitHub: paste Client ID and Secret from GitHub OAuth App
```

**Provider C — Auth0:**

```bash
# 1. Sign up at auth0.com
# 2. Create new application → Regular Web Application
# 3. Copy to .env.local:

AUTH0_SECRET=<generate with: openssl rand -hex 32>
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=https://your-tenant.auth0.com
AUTH0_CLIENT_ID=...
AUTH0_CLIENT_SECRET=...

# 4. Set Allowed Callback URLs: http://localhost:3000/api/auth/callback
# 5. Set Allowed Logout URLs: http://localhost:3000
```

**Verify**: Environment variables are set and provider dashboard shows the application.
**If failed**: Double-check URL format (no trailing slash). Ensure you're using the correct key type (publishable vs secret).

### Step 2: Install Dependencies and Configure Provider

**Duration**: 5-10 minutes
**Tool**: CLI + code editor

**Provider A — Clerk:** [src2]

```bash
npm install @clerk/nextjs
```

```typescript
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}

// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)'])

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) {
    await auth.protect()
  }
})

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
```

**Provider B — Supabase Auth:** [src1]

```bash
npm install @supabase/supabase-js @supabase/ssr
```

```typescript
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => request.cookies.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) => {
            request.cookies.set(name, value)
            supabaseResponse.cookies.set(name, value, options)
          })
        },
      },
    }
  )
  const { data: { user } } = await supabase.auth.getUser()

  if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  return supabaseResponse
}
```

**Verify**: No build errors. Application starts without auth-related console errors.
**If failed**: Check that environment variables are correctly named. Ensure middleware.ts is at the project root (not inside app/).

### Step 3: Build Authentication Pages

**Duration**: 10-15 minutes
**Tool**: Code editor

**Provider A — Clerk (pre-built):** [src2]

```typescript
// app/(auth)/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'

export default function SignInPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignIn />
    </div>
  )
}

// app/(auth)/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from '@clerk/nextjs'

export default function SignUpPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <SignUp />
    </div>
  )
}

// Set redirect URLs in .env.local:
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
```

**Provider B — Supabase (custom forms):** [src1] [src5]

```typescript
// app/(auth)/login/page.tsx
'use client'
import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'

export default function LoginPage() {
  const supabase = createClient()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  async function handleEmailLogin(e: React.FormEvent) {
    e.preventDefault()
    const { error } = await supabase.auth.signInWithPassword({ email, password })
    if (!error) window.location.href = '/dashboard'
  }

  async function handleMagicLink() {
    const { error } = await supabase.auth.signInWithOtp({
      email,
      options: { emailRedirectTo: `${window.location.origin}/auth/callback` }
    })
    if (!error) alert('Check your email for the magic link!')
  }

  async function handleOAuth(provider: 'google' | 'github') {
    await supabase.auth.signInWithOAuth({
      provider,
      options: { redirectTo: `${window.location.origin}/auth/callback` }
    })
  }

  return (
    <form onSubmit={handleEmailLogin}>
      <input type="email" value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Sign In</button>
      <button type="button" onClick={handleMagicLink}>Magic Link</button>
      <button type="button" onClick={() => handleOAuth('google')}>Google</button>
      <button type="button" onClick={() => handleOAuth('github')}>GitHub</button>
    </form>
  )
}

// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const code = searchParams.get('code')
  if (code) {
    const supabase = await createClient()
    await supabase.auth.exchangeCodeForSession(code)
  }
  return NextResponse.redirect(new URL('/dashboard', request.url))
}
```

**Verify**: Sign-up creates a new user. Login redirects to /dashboard. Social login opens provider consent screen. Magic link sends email.
**If failed**: Check OAuth redirect URIs match exactly. For Supabase, verify the callback route exists at `/auth/callback`. For Clerk, ensure the catch-all route uses `[[...sign-in]]` syntax.

### Step 4: Add Role-Based Authorization

**Duration**: 10 minutes
**Tool**: Code editor + provider dashboard

**Provider A — Clerk (Organizations):** [src2]

```typescript
// Use Clerk's built-in organizations and roles
// In Clerk Dashboard: Organizations → Enable

// app/(dashboard)/admin/page.tsx
import { auth } from '@clerk/nextjs/server'

export default async function AdminPage() {
  const { orgRole } = await auth()

  if (orgRole !== 'org:admin') {
    return <div>Access denied. Admin role required.</div>
  }

  return <div>Admin Dashboard</div>
}
```

**Provider B — Supabase (custom roles):** [src1]

```sql
-- Add role column to profiles table
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS
  role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'member', 'viewer'));

-- RLS policy: only admins can access admin routes
CREATE POLICY "Admins can manage all org items"
  ON items FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM profiles
      WHERE profiles.id = auth.uid()
      AND profiles.role = 'admin'
      AND profiles.org_id = items.org_id
    )
  );
```

```typescript
// lib/auth.ts — server-side role check
import { createClient } from '@/lib/supabase/server'

export async function requireRole(requiredRole: string) {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Unauthorized')

  const { data: profile } = await supabase
    .from('profiles')
    .select('role')
    .eq('id', user.id)
    .single()

  if (!profile || profile.role !== requiredRole) {
    throw new Error('Forbidden')
  }
  return user
}
```

**Verify**: Admin users can access /admin routes. Non-admin users see access denied. Role changes take effect immediately.
**If failed**: For Supabase, check RLS policies include role checks. For Clerk, verify organization settings are enabled in the dashboard.

### Step 5: Configure Production Settings

**Duration**: 10 minutes
**Tool**: Provider dashboard + DNS

```bash
# 1. Add production domain to provider:
#    Clerk: Dashboard → Domains → Add production domain
#    Supabase: Authentication → URL Configuration → Add site URL
#    Auth0: Application Settings → Allowed Callback URLs

# 2. Update OAuth redirect URIs for production:
#    Google Cloud Console: add https://yourdomain.com/auth/callback
#    GitHub: add https://yourdomain.com/auth/callback

# 3. Configure custom SMTP for magic links (Supabase):
#    Authentication → SMTP Settings → Enable custom SMTP
#    Use Resend (resend.com) — free for 3,000 emails/month

# 4. Set environment variables in hosting platform:
#    Vercel: Settings → Environment Variables
#    Add all NEXT_PUBLIC_* and secret keys for production
```

**Verify**: Auth flow works on production URL. Social login redirects back correctly. Magic link emails arrive in inbox (not spam).
**If failed**: Verify production domain is added to all OAuth providers. Check SMTP configuration with a test email. Ensure environment variables are set in the hosting platform.

## Output Schema

```json
{
  "output_type": "authentication_system",
  "format": "code + configured provider",
  "columns": [
    {"name": "auth_provider", "type": "string", "description": "Provider used (Clerk/Supabase/Auth0/Firebase)", "required": true},
    {"name": "auth_methods", "type": "string", "description": "Enabled methods (email, social, magic link, phone)", "required": true},
    {"name": "protected_routes", "type": "string", "description": "Routes requiring authentication", "required": true},
    {"name": "roles_defined", "type": "string", "description": "User roles and their permissions", "required": false},
    {"name": "oauth_providers", "type": "string", "description": "Social login providers configured", "required": false}
  ],
  "expected_row_count": "1",
  "sort_order": "N/A",
  "deduplication_key": "auth_provider"
}
```

## Quality Benchmarks

| Quality Metric | Minimum Acceptable | Good | Excellent |
|---------------|-------------------|------|-----------|
| Auth flow completeness | Signup + login + logout | + Social login + magic link | + Phone + MFA |
| Session security | Cookie-based sessions | + httpOnly + secure flags | + CSRF protection + token rotation |
| Protected routes | /dashboard requires auth | + Role-based route guards | + API route protection + middleware |
| Error UX | Generic error messages | + Specific error messages | + Inline validation + recovery flows |
| Time to authenticate | < 5 seconds | < 3 seconds | < 1 second (magic link excluded) |

**If below minimum**: Verify middleware is protecting routes. Check that session cookies have httpOnly and secure flags. Test all auth methods end-to-end.

## Error Handling

| Error | Likely Cause | Recovery Action |
|-------|-------------|----------------|
| `Invalid redirect URI` | Mismatch between provider dashboard and app | Copy exact redirect URI from error message to provider dashboard |
| `CORS error on auth callback` | Missing domain in provider's allowed origins | Add production domain to provider's CORS/allowed origins |
| Magic link email not received | Default SMTP flagged as spam | Configure custom SMTP via Resend or SendGrid |
| `auth/popup-closed-by-user` (Firebase) | User closed OAuth popup | Show retry button; ensure popup is not blocked by browser |
| Session not persisting on refresh | Cookies not set correctly in middleware | Check middleware runs on all routes; verify cookie options |
| `401 Unauthorized` on protected API route | Missing auth header or expired token | Check middleware forwards auth cookies; verify token refresh logic |

## Cost Breakdown

| Provider | Free Tier | At 10K MAU | At 50K MAU | At 100K MAU |
|----------|-----------|-----------|-----------|------------|
| Clerk | $0 (10K MAU) | $0 | $800/mo | $1,800/mo |
| Supabase Auth | $0 (50K MAU) | $0 | $0 | $162.50/mo |
| Auth0 | $0 (25K MAU) | $0 | $1,750/mo | $5,250/mo |
| Firebase Auth | $0 (50K MAU) | $0 | $0 | SMS costs only |
| **Winner at scale** | Supabase/Firebase | Supabase/Firebase | **Supabase** | **Supabase** |

## Anti-Patterns

### Wrong: Building custom authentication from scratch
Custom auth (password hashing, session management, CSRF protection, token rotation) takes 2-4 weeks and is the #1 source of security vulnerabilities in MVPs. Every auth provider on this list has teams of security engineers — your MVP does not. [src6]

### Correct: Use a managed auth provider from day one
Pick Clerk for fastest setup, Supabase for best value, Auth0 for enterprise compliance. Migrate to custom auth only if you have a dedicated security team (rare before Series B).

### Wrong: Storing sessions in localStorage
localStorage is accessible to any JavaScript on the page, making it vulnerable to XSS attacks. A single XSS vulnerability = all user sessions compromised. [src6]

### Correct: Use httpOnly cookies for session storage
Clerk and Supabase Auth (with @supabase/ssr) use httpOnly cookies by default. Auth0 uses session cookies with its SDK. Never store tokens in localStorage.

### Wrong: Using test OAuth credentials in production
Provider-supplied test credentials (visible in dashboard defaults) have rate limits, show "unverified app" warnings to users, and may stop working without notice. [src4]

### Correct: Register your own OAuth apps with Google and GitHub
Create production OAuth credentials with your app name, logo, and privacy policy URL. Submit for verification to remove the "unverified app" warning.

### Wrong: Not configuring redirect URIs for production
OAuth redirect URIs are environment-specific. Forgetting to add the production URL to Google/GitHub OAuth settings causes all social logins to fail after deployment. [src1]

### Correct: Add production URLs before deployment
Add both development (localhost:3000) and production (yourdomain.com) redirect URIs to every OAuth provider and the auth provider dashboard.

## When This Matters

Use this recipe when a developer needs to add authentication to an existing MVP project. Requires a project scaffold in place. This recipe covers provider setup, social login, magic links, protected routes, and role-based authorization — not user onboarding flows or profile management UI.

## Related Units

- [Coded MVP Architecture Templates](/software/mvp-development/coded-mvp-architecture-templates/2026) — scaffold project before auth setup
- [Payment Integration Setup](/software/mvp-development/payment-integration-setup/2026) — payments require authenticated users
- [Database Schema Design for MVPs](/software/mvp-development/database-schema-design-for-mvps/2026) — user profiles and auth tables
- [AI-Assisted Code Generation Workflow](/software/mvp-development/ai-assisted-code-generation-workflow/2026) — use AI to generate auth code
- [MVP Tech Stack Selection Guide](/software/mvp-development/mvp-tech-stack-selection-guide/2026) — choose auth provider as part of stack
