From saas-toolkit
Supabase authentication flows in Next.js App Router. Use when building signup, login, logout, password reset, OAuth, or protected routes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/saas-toolkit:authThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Implement Supabase authentication for Next.js App Router using `@supabase/ssr`.
Implement Supabase authentication for Next.js App Router using @supabase/ssr.
npm install @supabase/supabase-js @supabase/ssr
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Server client (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() { return cookieStore.getAll(); },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}
Browser client (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!
);
}
Middleware client (lib/supabase/middleware.ts):
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function updateSession(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() { return request.cookies.getAll(); },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
);
supabaseResponse = NextResponse.next({ request });
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
);
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
const url = request.nextUrl.clone();
url.pathname = '/login';
return NextResponse.redirect(url);
}
return supabaseResponse;
}
middleware.ts (project root):
import { updateSession } from '@/lib/supabase/middleware';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};
app/(protected)/layout.tsx:
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
export default async function ProtectedLayout({ children }) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect('/login');
return <>{children}</>;
}
app/auth/callback/route.ts:
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get('code');
const next = searchParams.get('next') ?? '/dashboard';
if (code) {
const supabase = await createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) return NextResponse.redirect(`${origin}${next}`);
}
return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}
'use server';
import { createClient } from '@/lib/supabase/server';
import { redirect } from 'next/navigation';
export async function signup(formData: FormData) {
const supabase = await createClient();
const { error } = await supabase.auth.signUp({
email: formData.get('email') as string,
password: formData.get('password') as string,
});
if (error) return { error: error.message };
redirect('/check-email');
}
'use server';
import { createClient } from '@/lib/supabase/server';
export async function resetPassword(formData: FormData) {
const supabase = await createClient();
const { error } = await supabase.auth.resetPasswordForEmail(
formData.get('email') as string,
{ redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback?next=/update-password` }
);
if (error) return { error: error.message };
return { success: true };
}
getUser() (not getSession()) for auth checks — getUser() validates the JWT server-sideawait supabase.auth.signOut() then router.refresh()createClient from @supabase/supabase-js directly — always use @supabase/ssrgetSession() for authorization checks — use getUser()npx claudepluginhub mickaelmamani/saas-toolkit --plugin saas-toolkitSets up Supabase Auth with Next.js App Router: browser/server clients, auth middleware for route protection and session refresh, and OAuth callback handling.
Configure Supabase authentication providers (OAuth, JWT, email). Use when setting up authentication, configuring OAuth providers (Google/GitHub/Discord), implementing auth flows, configuring JWT settings, or when user mentions Supabase auth, social login, authentication setup, or auth configuration.
Implements complete Next.js authentication with Auth.js: OAuth providers (GitHub/Google), credentials login, Prisma adapter, session management (JWT/DB), middleware-protected routes, RBAC, and login forms.