CoreMVP
Features

Authentication

Supabase Auth integration with server-side session management.

Overview

Authentication is handled by Supabase Auth with server-side session management via Next.js middleware.

Supported Methods

  • Email/Password with verification
  • Magic links (OTP)
  • OAuth (Google, GitHub, etc.)

Architecture

service.ts
types.ts
sign-up/route.ts
sign-in/password/route.ts
sign-in/email/route.ts
sign-out/route.ts
oauth/route.ts

How It Works

1. User submits form → calls API route
2. API route → calls auth service
3. Auth service → calls Supabase Auth
4. Supabase returns session/tokens
5. Middleware refreshes session on each request

Auth Service

The auth service (src/services/auth/service.ts) contains all authentication logic. It uses the "server-only" pragma to prevent client-side imports:

src/services/auth/service.ts
import "server-only";

import { createSupabaseServerClient } from "@/lib/supabase/server";

export async function signUp({ email, password }: SignUpParams) {
  const supabase = await createSupabaseServerClient();
  const { error, data } = await supabase.auth.signUp({
    email,
    password,
    options: {
      emailRedirectTo: getURL("/auth/callback"),
    },
  });

  if (error) {
    return { ok: false, redirectPath: getErrorRedirect("/signup", error.message) };
  }

  return { ok: true, redirectPath: "/" };
}

export async function signInWithPassword({ email, password }: PasswordSignInParams) {
  const supabase = await createSupabaseServerClient();
  const { error, data } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    return { ok: false, redirectPath: getErrorRedirect("/signin", error.message) };
  }

  return { ok: true, redirectPath: "/" };
}

export async function signOut() {
  const supabase = await createSupabaseServerClient();
  await supabase.auth.signOut();
  return { ok: true, redirectPath: "/signin" };
}

API Routes

API routes call the auth service and return JSON:

src/app/api/auth/sign-up/route.ts
import { NextResponse } from 'next/server';
import { signUp } from '@/services/auth';

export async function POST(request: Request) {
  const body = await request.json();
  const result = await signUp(body);
  return NextResponse.json(result, {
    status: result.ok ? 200 : 400,
  });
}

Client-Side Usage

Sign Up

const response = await fetch('/api/auth/sign-up', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password }),
});
const result = await response.json();

if (result.ok) {
  window.location.href = result.redirectPath;
}

Using the Auth Hooks

The template includes React hooks for auth mutations:

src/hooks/auth/use-auth-mutations.ts
export function useSignUp() {
  return useMutation({
    mutationFn: async ({ email, password }) => {
      const res = await fetch('/api/auth/sign-up', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
      });
      return res.json();
    },
  });
}

In components:

const { mutate: signUp, isPending } = useSignUp();

signUp({ email, password }, {
  onSuccess: (result) => {
    if (result.ok) router.push(result.redirectPath);
  },
});

Session Management

Middleware

The middleware (src/middleware.ts) refreshes the session on each request:

src/middleware.ts
import { updateSession } from '@/lib/supabase/middleware';

export async function middleware(request: NextRequest) {
  return await updateSession(request);
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg)$).*)'],
};

Getting the Current User

In server components or API routes:

import { createSupabaseServerClient } from '@/lib/supabase/server';

const supabase = await createSupabaseServerClient();
const { data: { user } } = await supabase.auth.getUser();

In client components, use the session hook:

import { useSession } from '@/hooks/auth';

const { user, isLoading } = useSession();

Protected Routes

Check for authentication in page components:

src/app/dashboard/page.tsx
import { redirect } from 'next/navigation';
import { createSupabaseServerClient } from '@/lib/supabase/server';

export default async function DashboardPage() {
  const supabase = await createSupabaseServerClient();
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    redirect('/signin');
  }

  return <Dashboard user={user} />;
}

OAuth Setup

Enable Provider in Supabase

Go to Supabase Dashboard → Authentication → Providers

Configure OAuth App

Create OAuth app in Google/GitHub console and get credentials

Add Credentials to Supabase

Enter Client ID and Secret in Supabase provider settings

Set Redirect URLs

Add https://<project-ref>.supabase.co/auth/v1/callback to the OAuth provider's allowed redirects. This is the provider callback, not your app's hosted URL.

Configure the Hosted App URL

After your hosted domain is assigned, open Supabase URL Configuration. Set Site URL to https://<your-domain>, add https://<your-domain>/** to Redirect URLs, and save both fields before testing OAuth.

Database Trigger

New users are automatically added to the users table via a database trigger:

supabase/migrations/handle_new_user.sql
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
  INSERT INTO public.users (id, email)
  VALUES (new.id, new.email);
  RETURN new;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();

Was this page helpful?

On this page