🎯

supabase-auth

🎯Skill

from dadbodgeoff/drift

VibeIndex|
What it does

Implements secure, cookie-based Supabase authentication with SSR support for Next.js, handling email/password login and user sessions.

πŸ“¦

Part of

dadbodgeoff/drift(69 items)

supabase-auth

Installation

npm installInstall npm package
npm install -g driftdetect
npm installInstall npm package
npm install -g driftdetect@latest
npm installInstall npm package
npm install -g driftdetect-mcp
Claude Desktop ConfigurationAdd this to your claude_desktop_config.json
{ "mcpServers": { "drift": { "command": "driftdetect-mcp" } } ...
πŸ“– Extracted from docs: dadbodgeoff/drift
4Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Implement Supabase authentication with SSR support. Email/password auth with automatic session management via cookies. Includes login, signup, email confirmation, and user profiles.

Overview

# Supabase Authentication

Cookie-based authentication with SSR support.

When to Use This Skill

  • Need authentication without rolling your own
  • Building a Next.js app with SSR
  • Want email/password + social auth options
  • Need automatic session refresh

Core Concepts

  1. Browser client - For client components
  2. Server client - For API routes and server components
  3. Cookie-based sessions - Automatic refresh via middleware
  4. User profiles - Extended user data in your database

TypeScript Implementation

Browser Client

```typescript

// lib/supabase.ts

import { createBrowserClient } from '@supabase/ssr';

export function createClient() {

return createBrowserClient(

process.env.NEXT_PUBLIC_SUPABASE_URL!,

process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

);

}

```

Server Client

```typescript

// lib/supabase-server.ts

import { createServerClient, type CookieOptions } from '@supabase/ssr';

import { cookies } from 'next/headers';

export async function createServerSupabaseClient() {

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: { name: string; value: string; options: CookieOptions }[]) {

try {

cookiesToSet.forEach(({ name, value, options }) =>

cookieStore.set(name, value, options)

);

} catch {

// Called from Server Component - ignore

}

},

},

}

);

}

```

Login Page

```typescript

// app/login/page.tsx

'use client';

import { useState } from 'react';

import { useRouter, useSearchParams } from 'next/navigation';

import { createClient } from '@/lib/supabase';

export default function LoginPage() {

const [email, setEmail] = useState('');

const [password, setPassword] = useState('');

const [error, setError] = useState(null);

const [isLoading, setIsLoading] = useState(false);

const router = useRouter();

const searchParams = useSearchParams();

const redirectTo = searchParams.get('redirectTo') || '/dashboard';

const supabase = createClient();

const handleLogin = async (e: React.FormEvent) => {

e.preventDefault();

setError(null);

setIsLoading(true);

try {

const { error } = await supabase.auth.signInWithPassword({

email,

password,

});

if (error) throw error;

router.push(redirectTo);

router.refresh();

} catch (err) {

setError(err instanceof Error ? err.message : 'Login failed');

} finally {

setIsLoading(false);

}

};

return (

Sign In

type="email"

value={email}

onChange={(e) => setEmail(e.target.value)}

placeholder="Email"

required

className="w-full px-4 py-2 border rounded"

/>

type="password"

value={password}

onChange={(e) => setPassword(e.target.value)}

placeholder="Password"

required

className="w-full px-4 py-2 border rounded"

/>

{error && (

{error}

)}

type="submit"

disabled={isLoading}

className="w-full py-2 bg-blue-600 text-white rounded disabled:opacity-50"

>

{isLoading ? 'Signing in...' : 'Sign In'}

Don't have an account? Sign up

);

}

```

Signup Page

```typescript

// app/signup/page.tsx

'use client';

import { useState } from 'react';

import { createClient } from '@/lib/supabase';

export default function SignupPage() {

const [email, setEmail] = useState('');

const [password, setPassword] = useState('');

const [name, setName] = useState('');

const [error, setError] = useState(null);

const [success, setSuccess] = useState(false);

const [isLoading, setIsLoading] = useState(false);

const handleSignup = async (e: React.FormEvent) => {

e.preventDefault();

setError(null);

setIsLoading(true);

try {

const supabase = createClient();

const { data, error: signUpError } = await supabase.auth.signUp({

email,

password,

options: {

data: { display_name: name },

emailRedirectTo: ${window.location.origin}/auth/callback,

},

});

if (signUpError) throw signUpError;

if (data?.user?.identities?.length === 0) {

setError('This email is already registered.');

return;

}

setSuccess(true);

} catch (err) {

setError(err instanceof Error ? err.message : 'Signup failed');

} finally {

setIsLoading(false);

}

};

if (success) {

return (

Check your email

We sent a confirmation link to {email}

);

}

return (

Create Account

type="text"

value={name}

onChange={(e) => setName(e.target.value)}

placeholder="Name"

className="w-full px-4 py-2 border rounded"

/>

type="email"

value={email}

onChange={(e) => setEmail(e.target.value)}

placeholder="Email"

required

className="w-full px-4 py-2 border rounded"

/>

type="password"

value={password}

onChange={(e) => setPassword(e.target.value)}

placeholder="Password (min 6 characters)"

minLength={6}

required

className="w-full px-4 py-2 border rounded"

/>

{error && (

{error}

)}

type="submit"

disabled={isLoading}

className="w-full py-2 bg-blue-600 text-white rounded disabled:opacity-50"

>

{isLoading ? 'Creating account...' : 'Create Account'}

);

}

```

Auth Callback Route

```typescript

// app/auth/callback/route.ts

import { createServerClient } from '@supabase/ssr';

import { cookies } from 'next/headers';

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 cookieStore = await cookies();

const supabase = 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)

);

},

},

}

);

const { error } = await supabase.auth.exchangeCodeForSession(code);

if (!error) {

return NextResponse.redirect(${origin}${next});

}

}

return NextResponse.redirect(${origin}/login?error=auth_callback_error);

}

```

useUser Hook

```typescript

// hooks/useUser.ts

'use client';

import { useEffect, useState, useCallback } from 'react';

import { createClient } from '@/lib/supabase';

import type { User } from '@supabase/supabase-js';

interface UserProfile {

id: string;

display_name: string | null;

subscription_tier: 'free' | 'pro';

}

export function useUser() {

const [user, setUser] = useState(null);

const [profile, setProfile] = useState(null);

const [isLoading, setIsLoading] = useState(true);

const supabase = createClient();

const fetchProfile = useCallback(async (userId: string) => {

const { data } = await supabase

.from('user_profiles')

.select('*')

.eq('id', userId)

.single();

return data as UserProfile | null;

}, [supabase]);

const signOut = useCallback(async () => {

await supabase.auth.signOut();

setUser(null);

setProfile(null);

}, [supabase]);

useEffect(() => {

const getSession = async () => {

const { data: { session } } = await supabase.auth.getSession();

if (session?.user) {

setUser(session.user);

const profileData = await fetchProfile(session.user.id);

setProfile(profileData);

}

setIsLoading(false);

};

getSession();

const { data: { subscription } } = supabase.auth.onAuthStateChange(

async (event, session) => {

if (event === 'SIGNED_IN' && session?.user) {

setUser(session.user);

const profileData = await fetchProfile(session.user.id);

setProfile(profileData);

} else if (event === 'SIGNED_OUT') {

setUser(null);

setProfile(null);

}

}

);

return () => subscription.unsubscribe();

}, [supabase, fetchProfile]);

return {

user,

profile,

tier: profile?.subscription_tier ?? 'free',

isLoading,

signOut,

};

}

```

Database Migration

```sql

-- migrations/001_user_profiles.sql

CREATE TABLE user_profiles (

id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,

display_name VARCHAR(255),

subscription_tier VARCHAR(20) NOT NULL DEFAULT 'free',

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

);

-- Auto-create profile on signup

CREATE OR REPLACE FUNCTION handle_new_user()

RETURNS TRIGGER AS $$

BEGIN

INSERT INTO user_profiles (id, display_name)

VALUES (NEW.id, NEW.raw_user_meta_data->>'display_name');

RETURN NEW;

END;

$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created

AFTER INSERT ON auth.users

FOR EACH ROW EXECUTE FUNCTION handle_new_user();

-- RLS

ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own profile" ON user_profiles

FOR SELECT USING (auth.uid() = id);

CREATE POLICY "Users can update own profile" ON user_profiles

FOR UPDATE USING (auth.uid() = id);

```

Best Practices

  1. Use SSR client - Server components need cookie access
  2. Refresh in middleware - Keep sessions alive automatically
  3. Auto-create profiles - Database trigger on signup
  4. Enable RLS - Row-level security on all user tables
  5. Handle email confirmation - Check for empty identities array

Common Mistakes

  • Using browser client in server components
  • Not refreshing session in middleware
  • Missing RLS policies on user data
  • Not handling email confirmation flow
  • Forgetting to call router.refresh() after login

Related Skills

  • [Middleware Protection](../middleware-protection/)
  • [Row Level Security](../row-level-security/)
  • [JWT Auth](../jwt-auth/)

More from this repository10

🎯
feature-flags🎯Skill

Enables controlled feature rollouts, A/B testing, and selective feature access through configurable flags for gradual deployment and user targeting.

🎯
design-tokens🎯Skill

Generates a comprehensive, type-safe design token system with WCAG AA color compliance and multi-framework support for consistent visual design.

🎯
file-uploads🎯Skill

Securely validates, scans, and processes file uploads with multi-stage checks, malware detection, and race condition prevention.

🎯
ai-coaching🎯Skill

Guides users through articulating creative intent by extracting structured parameters and detecting conversation readiness.

🎯
environment-config🎯Skill

Validates and centralizes environment variables with type safety, fail-fast startup checks, and multi-environment support.

🎯
community-feed🎯Skill

Generates efficient social feed with cursor pagination, trending algorithms, and engagement tracking for infinite scroll experiences.

🎯
cloud-storage🎯Skill

Enables secure, multi-tenant cloud file storage with signed URLs, direct uploads, and visibility control for user-uploaded assets.

🎯
email-service🎯Skill

Simplifies email sending, templating, and tracking with robust SMTP integration and support for multiple email providers and transactional workflows.

🎯
error-sanitization🎯Skill

Sanitizes error messages by logging full details server-side while exposing only generic, safe messages to prevent sensitive information leakage.

🎯
batch-processing🎯Skill

Optimizes database operations by collecting and batching independent records, improving throughput by 30-40% with built-in fallback processing.