Skip to main content
RapidDev - Software Development Agency

How to Build Authentication system with V0

Build a multi-provider authentication system with V0 using Next.js and Supabase Auth. You'll get email/password sign-up, Google and GitHub OAuth, protected routes with middleware, profile management, and SSR session handling — all in about 1-2 hours without any local setup.

What you'll build

  • Email/password registration and login forms with shadcn/ui Card, Input, and Label components
  • Google and GitHub OAuth login with provider icons and one-click sign-in buttons
  • OAuth callback handler at app/auth/callback/route.ts for secure redirect processing
  • Protected route layout using createServerClient that redirects unauthenticated users
  • User profile page with avatar, name editing, and onboarding flow
  • SSR session hydration using @supabase/ssr middleware to prevent flash of unauthenticated content
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 FreeApril 2026RapidDev Engineering Team
TL;DR

Build a multi-provider authentication system with V0 using Next.js and Supabase Auth. You'll get email/password sign-up, Google and GitHub OAuth, protected routes with middleware, profile management, and SSR session handling — all in about 1-2 hours without any local setup.

What you're building

Authentication is the first thing every app needs. Whether you are building a SaaS, marketplace, or community platform, you need secure user registration, login, OAuth providers, and protected routes before you can build any user-facing feature.

V0 generates the complete authentication flow from prompts — login forms, OAuth buttons, callback handlers, and protected layouts. Supabase Auth via the Connect panel provides email/password, OAuth, and session management out of the box, with automatic JWT handling and refresh token rotation.

The architecture uses Supabase Auth for user management (auth.users table auto-managed), a custom public.profiles table for app-specific user data, @supabase/ssr for cookie-based session handling in Server Components, and Next.js middleware for route protection. Client Components handle the interactive login forms while Server Components verify sessions and fetch user data.

Final result

A complete authentication system with email/password registration, Google and GitHub OAuth, protected routes, user profiles with avatars, and secure SSR session handling using Supabase Auth.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
Supabase AuthAuth
SupabaseDatabase

Prerequisites

  • A V0 account (free tier works for this project)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • Google Cloud Console OAuth credentials (for Google sign-in)
  • GitHub OAuth App credentials (for GitHub sign-in)

Build steps

1

Set up Supabase Auth and the profiles table

Create a new V0 project and connect Supabase via the Connect panel. Then create a custom profiles table that extends the built-in auth.users with app-specific fields. Add a database trigger that automatically creates a profile row when a new user signs up.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an authentication system with Supabase Auth. Set up:
3// 1. A custom profiles table: id (uuid PK REFERENCES auth.users ON DELETE CASCADE), email (text), full_name (text), avatar_url (text), role (text DEFAULT 'user'), onboarded (boolean DEFAULT false), created_at (timestamptz DEFAULT now())
4// 2. A database trigger on auth.users INSERT that auto-creates a profiles row with the user's email
5// 3. RLS policies on profiles: users can read any profile, users can update only their own profile
6// 4. Enable email/password auth in Supabase (it's on by default)
7// Generate the SQL migration for the profiles table, trigger, and RLS policies.

Pro tip: The Connect panel auto-provisions NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. You will also need SUPABASE_SERVICE_ROLE_KEY in the Vars tab for server-side operations that bypass RLS.

Expected result: Supabase is connected, the profiles table is created with a trigger that auto-creates profile rows on signup, and RLS policies are in place.

2

Build the login and signup pages with OAuth providers

Create the authentication forms using shadcn/ui components. The login page has email/password fields plus OAuth buttons for Google and GitHub. Both forms use Supabase Auth client methods for the actual authentication.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build authentication pages:
3// 1. app/login/page.tsx - Login form with:
4// - shadcn/ui Card containing the form
5// - Input for email and password with Label components
6// - Button for "Sign in with email"
7// - Separator with "or continue with" text
8// - Two OAuth Buttons: Google (with icon) and GitHub (with icon)
9// - Link to /signup for new users
10// - Use Supabase createBrowserClient for auth methods
11// - signInWithPassword for email, signInWithOAuth for Google/GitHub
12// - Redirect to /dashboard on success
13// 2. app/signup/page.tsx - Registration form with:
14// - Same Card layout as login
15// - Additional Input for full_name
16// - signUp method with email, password, and metadata
17// - Show "Check your email" message after signup
18// - Link to /login for existing users
19// Both pages should be 'use client' with useState for loading/error states

Expected result: Login and signup pages render with shadcn/ui Cards, email/password inputs, and OAuth provider buttons. Forms handle loading and error states.

3

Create the OAuth callback handler and middleware

Build the callback route that handles OAuth redirects from Google and GitHub. Then create the Next.js middleware that refreshes the Supabase session on every request using cookie-based auth from @supabase/ssr.

middleware.ts
1import { createServerClient } from '@supabase/ssr'
2import { NextResponse, type NextRequest } from 'next/server'
3
4export async function middleware(request: NextRequest) {
5 let supabaseResponse = NextResponse.next({ request })
6
7 const supabase = createServerClient(
8 process.env.NEXT_PUBLIC_SUPABASE_URL!,
9 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10 {
11 cookies: {
12 getAll() {
13 return request.cookies.getAll()
14 },
15 setAll(cookiesToSet) {
16 cookiesToSet.forEach(({ name, value, options }) =>
17 request.cookies.set(name, value)
18 )
19 supabaseResponse = NextResponse.next({ request })
20 cookiesToSet.forEach(({ name, value, options }) =>
21 supabaseResponse.cookies.set(name, value, options)
22 )
23 },
24 },
25 }
26 )
27
28 const { data: { user } } = await supabase.auth.getUser()
29
30 if (
31 !user &&
32 !request.nextUrl.pathname.startsWith('/login') &&
33 !request.nextUrl.pathname.startsWith('/signup') &&
34 !request.nextUrl.pathname.startsWith('/auth') &&
35 request.nextUrl.pathname !== '/'
36 ) {
37 const url = request.nextUrl.clone()
38 url.pathname = '/login'
39 return NextResponse.redirect(url)
40 }
41
42 return supabaseResponse
43}
44
45export const config = {
46 matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
47}

Pro tip: The middleware refreshes the Supabase session cookie on every request. This prevents the session from expiring while the user is active and eliminates the flash of unauthenticated content in Server Components.

Expected result: The middleware intercepts all requests, refreshes the session, and redirects unauthenticated users to /login. OAuth callbacks are processed at /auth/callback.

4

Build the protected dashboard layout with session data

Create a shared layout for all protected pages that verifies the session server-side and provides user data to child components. This layout also handles the case where a user is authenticated but has not completed onboarding.

app/(protected)/layout.tsx
1import { createServerClient } from '@supabase/ssr'
2import { cookies } from 'next/headers'
3import { redirect } from 'next/navigation'
4import { UserNav } from '@/components/user-nav'
5
6export default async function ProtectedLayout({
7 children,
8}: {
9 children: React.ReactNode
10}) {
11 const cookieStore = await cookies()
12 const supabase = createServerClient(
13 process.env.NEXT_PUBLIC_SUPABASE_URL!,
14 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
15 {
16 cookies: {
17 getAll() {
18 return cookieStore.getAll()
19 },
20 },
21 }
22 )
23
24 const { data: { user } } = await supabase.auth.getUser()
25 if (!user) redirect('/login')
26
27 const { data: profile } = await supabase
28 .from('profiles')
29 .select('full_name, avatar_url, role, onboarded')
30 .eq('id', user.id)
31 .single()
32
33 if (!profile?.onboarded) redirect('/onboarding')
34
35 return (
36 <div className="min-h-screen">
37 <header className="border-b px-6 py-3 flex items-center justify-between">
38 <h1 className="text-lg font-semibold">Dashboard</h1>
39 <UserNav
40 name={profile.full_name}
41 email={user.email!}
42 avatar={profile.avatar_url}
43 />
44 </header>
45 <main className="p-6">{children}</main>
46 </div>
47 )
48}

Expected result: All pages under the (protected) route group verify the session, fetch the user profile, redirect to onboarding if needed, and render a header with the user's avatar and name.

5

Add the profile management page with avatar upload

Create a profile page where authenticated users can update their name, upload an avatar to Supabase Storage, and view their account details. Use a Server Action for the update to keep the logic server-side.

app/actions/profile.ts
1'use server'
2
3import { createClient } from '@supabase/supabase-js'
4import { revalidatePath } from 'next/cache'
5
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function updateProfile(
12 userId: string,
13 fullName: string,
14 avatarUrl?: string
15) {
16 const updates: Record<string, unknown> = { full_name: fullName }
17 if (avatarUrl) updates.avatar_url = avatarUrl
18
19 const { error } = await supabase
20 .from('profiles')
21 .update(updates)
22 .eq('id', userId)
23
24 if (error) throw new Error(error.message)
25 revalidatePath('/profile')
26}
27
28export async function completeOnboarding(userId: string, fullName: string) {
29 const { error } = await supabase
30 .from('profiles')
31 .update({ full_name: fullName, onboarded: true })
32 .eq('id', userId)
33
34 if (error) throw new Error(error.message)
35 revalidatePath('/')
36}

Expected result: Users can update their name and avatar. The onboarding action marks the profile as complete and redirects to the dashboard.

Complete code

middleware.ts
1import { createServerClient } from '@supabase/ssr'
2import { NextResponse, type NextRequest } from 'next/server'
3
4export async function middleware(request: NextRequest) {
5 let supabaseResponse = NextResponse.next({ request })
6
7 const supabase = createServerClient(
8 process.env.NEXT_PUBLIC_SUPABASE_URL!,
9 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10 {
11 cookies: {
12 getAll() {
13 return request.cookies.getAll()
14 },
15 setAll(cookiesToSet) {
16 cookiesToSet.forEach(({ name, value }) =>
17 request.cookies.set(name, value)
18 )
19 supabaseResponse = NextResponse.next({ request })
20 cookiesToSet.forEach(({ name, value, options }) =>
21 supabaseResponse.cookies.set(name, value, options)
22 )
23 },
24 },
25 }
26 )
27
28 const {
29 data: { user },
30 } = await supabase.auth.getUser()
31
32 const publicPaths = ['/login', '/signup', '/auth', '/']
33 const isPublic = publicPaths.some((p) =>
34 request.nextUrl.pathname.startsWith(p)
35 )
36
37 if (!user && !isPublic) {
38 const url = request.nextUrl.clone()
39 url.pathname = '/login'
40 return NextResponse.redirect(url)
41 }
42
43 return supabaseResponse
44}
45
46export const config = {
47 matcher: [
48 '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
49 ],
50}

Customization ideas

Add magic link authentication

Add a passwordless login option using Supabase Auth's signInWithOtp method that sends a login link to the user's email — no password needed.

Add multi-factor authentication

Enable TOTP-based MFA using Supabase Auth's MFA methods to add a second factor verification step after password login.

Add social providers (Apple, Microsoft)

Extend the OAuth buttons to include Apple and Microsoft sign-in by configuring the providers in Supabase Dashboard and adding corresponding buttons to the login form.

Add email verification enforcement

Configure Supabase to require email verification before users can access protected routes by checking the email_confirmed_at field in the middleware.

Common pitfalls

Pitfall: Using createBrowserClient in Server Components

How to avoid: Use createServerClient from @supabase/ssr in Server Components and middleware. Use createBrowserClient only in Client Components marked with 'use client'.

Pitfall: Forgetting to set OAuth redirect URLs in the provider console

How to avoid: Set the redirect URL to https://your-supabase-project.supabase.co/auth/v1/callback in both the Google Cloud Console and GitHub OAuth App settings. After deploying to a custom domain, add that URL too.

Pitfall: Exposing SUPABASE_SERVICE_ROLE_KEY with NEXT_PUBLIC_ prefix

How to avoid: Store SUPABASE_SERVICE_ROLE_KEY in V0's Vars tab without any prefix. Only SUPABASE_URL and SUPABASE_ANON_KEY should use NEXT_PUBLIC_ if needed on the client.

Best practices

  • Use @supabase/ssr with createServerClient in Server Components and middleware for secure cookie-based session handling
  • Use createBrowserClient only in 'use client' components that need to call Supabase Auth methods like signInWithPassword
  • Store SUPABASE_SERVICE_ROLE_KEY in V0's Vars tab without NEXT_PUBLIC_ prefix — it should only be accessible server-side
  • Add a database trigger on auth.users INSERT to auto-create profiles rows so your app data stays in sync with Supabase Auth
  • Use middleware to refresh sessions on every request, preventing session expiry during active use
  • Use Design Mode (Option+D) to visually adjust login form layout, OAuth button styling, and card spacing without spending credits
  • Always redirect to a meaningful page after OAuth callback — not back to the login page
  • Set up RLS policies on the profiles table so users can only update their own profile data

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a multi-provider authentication system with Next.js App Router and Supabase Auth. I need email/password signup, Google and GitHub OAuth, SSR session handling with @supabase/ssr middleware, and protected routes. Help me design the middleware pattern that refreshes sessions and redirects unauthenticated users.

Build Prompt

Build a Supabase Auth middleware for Next.js App Router that refreshes the session cookie on every request using @supabase/ssr createServerClient. The middleware should: get all cookies from the request, create a Supabase client that can read and set cookies, call getUser() to refresh the session, redirect unauthenticated users to /login for protected paths, and pass through public paths like /login, /signup, and /auth/callback.

Frequently asked questions

Should I use Supabase Auth, Clerk, or Auth.js for my V0 project?

Supabase Auth is the best choice if you are already using Supabase for your database — it keeps everything in one platform with seamless RLS integration. Clerk is fastest if you want pre-built UI components without writing any auth forms. Auth.js is the free open-source option for maximum flexibility.

How do I prevent the flash of unauthenticated content?

Use the @supabase/ssr middleware pattern that refreshes the session on every request. Server Components check the session before rendering, so unauthenticated users are redirected at the server level before any HTML is sent to the browser.

What V0 plan do I need for an authentication system?

The V0 free plan works for a basic authentication system. The project requires a few pages (login, signup, profile) and one middleware file, which fits within the free credit allocation.

How do I add Google OAuth to my V0 project?

First, create OAuth credentials in the Google Cloud Console with the redirect URL pointing to your Supabase project. Then enable Google as a provider in Supabase Dashboard under Authentication, then Providers. The login page calls supabase.auth.signInWithOAuth with provider set to google.

How do I deploy the auth system to production?

Click Share then Publish to Production in V0. After deployment, update the OAuth redirect URLs in Google and GitHub to include your production domain. Also update the Site URL in Supabase Dashboard under Authentication, then URL Configuration.

Can I use this authentication system with other databases?

Supabase Auth is tightly integrated with Supabase PostgreSQL. If you want to use a different database, consider Clerk or Auth.js v5 instead, which are database-agnostic and work with any backend.

Can RapidDev help build a custom authentication system?

Yes. RapidDev has built 600+ apps with complex auth systems including multi-tenant SSO, enterprise SAML, and custom MFA flows. Book a free consultation to discuss your authentication requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.