Authentication & User Management
LaunchKit uses Supabase Auth for comprehensive user authentication and management. This guide covers setup, configuration, and user management features.
Overview
Section titled “Overview”LaunchKit’s authentication system includes:
- Multiple sign-in methods: Email/password, OAuth, magic links
- User profile management with automatic profile creation
- Session management with secure cookies
- Password reset and email verification
- Admin user management and role-based access
Supabase Auth Configuration
Section titled “Supabase Auth Configuration”1. Authentication Settings
Section titled “1. Authentication Settings”In your Supabase Dashboard:
- Go to Authentication → Settings
- Configure the following:
Site URL: Your app’s base URL
# Development
http://localhost:3000
# Production
https://yourdomain.com
Additional Redirect URLs:
# Development
http://localhost:3000/api/auth/callback
http://localhost:3000/dashboard
# Production
https://yourdomain.com/api/auth/callback
https://yourdomain.com/dashboard
2. Email Templates
Section titled “2. Email Templates”Configure email templates in Authentication → Email Templates:
Subject: Confirm your signup
Body:
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p><a href="{{ .ConfirmationURL }}">Confirm your email</a></p>
Subject: Your magic link
Body:
<h2>Magic Link</h2>
<p>Follow this link to sign in:</p>
<p><a href="{{ .ConfirmationURL }}">Sign In</a></p>
Subject: Reset your password
Body:
<h2>Reset Password</h2>
<p>Follow this link to reset your password:</p>
<p><a href="{{ .ConfirmationURL }}">Reset Password</a></p>
3. Auth Providers
Section titled “3. Auth Providers”Enable desired OAuth providers in Authentication → Providers:
Google OAuth Setup
Section titled “Google OAuth Setup”-
Go to Google Cloud Console
-
Create a new project or select existing
-
Enable Google+ API
-
Create OAuth 2.0 credentials:
- Application type: Web application
- Authorized origins:
https://[your-project].supabase.co
- Authorized redirect URIs:
https://[your-project].supabase.co/auth/v1/callback
-
In Supabase, add your Google credentials:
Client ID: your-google-client-id Client Secret: your-google-client-secret
GitHub OAuth Setup
Section titled “GitHub OAuth Setup”-
Go to GitHub → Settings → Developer settings → OAuth Apps
-
Create new OAuth App:
- Homepage URL:
https://yourdomain.com
- Authorization callback URL:
https://[your-project].supabase.co/auth/v1/callback
- Homepage URL:
-
In Supabase, add your GitHub credentials:
Client ID: your-github-client-id Client Secret: your-github-client-secret
Authentication Flow
Section titled “Authentication Flow”1. Client-Side Authentication
Section titled “1. Client-Side Authentication”LaunchKit uses the Supabase client for authentication:
// /libs/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!
);
}
2. Server-Side Authentication
Section titled “2. Server-Side Authentication”For server-side operations, use the server client:
// /libs/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) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
} catch {
// Handle Server Component context
}
},
},
}
);
}
Sign-In Methods
Section titled “Sign-In Methods”1. Email/Password Authentication
Section titled “1. Email/Password Authentication”const { data, error } = await supabase.auth.signUp({
email: '[email protected]',
password: 'password123',
options: {
data: {
full_name: 'John Doe',
},
},
});
if (error) {
console.error('Sign up error:', error.message);
} else if (data.user && !data.user.email_confirmed_at) {
// Email confirmation required
console.log('Check email for confirmation');
} else {
// Success, redirect to dashboard
router.push('/dashboard');
}
const { error } = await supabase.auth.signInWithPassword({
email: '[email protected]',
password: 'password123',
});
if (error) {
console.error('Sign in error:', error.message);
} else {
// Success, redirect to dashboard
router.push('/dashboard');
}
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Sign out error:', error.message);
} else {
// Success, redirect to home
router.push('/');
}
2. OAuth Authentication
Section titled “2. OAuth Authentication”const handleOAuthSignIn = async (provider: Provider) => {
const redirectURL = getBaseUrl() + '/api/auth/callback';
await supabase.auth.signInWithOAuth({
provider, // 'google', 'github', etc.
options: {
redirectTo: redirectURL,
},
});
};
3. Magic Link Authentication
Section titled “3. Magic Link Authentication”const { error } = await supabase.auth.signInWithOtp({
email: '[email protected]',
options: {
emailRedirectTo: `${window.location.origin}/api/auth/callback`,
},
});
if (error) {
console.error('Magic link error:', error.message);
} else {
console.log('Check your email for the magic link');
}
User Profile Management
Section titled “User Profile Management”1. Automatic Profile Creation
Section titled “1. Automatic Profile Creation”When a user signs up, a profile is automatically created via database trigger:
-- Function to create profile on signup
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id, email, name, avatar_url, created_at, updated_at, last_login)
VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'full_name', NEW.raw_user_meta_data->>'name'),
NEW.raw_user_meta_data->>'avatar_url',
(now() AT TIME ZONE 'UTC'),
(now() AT TIME ZONE 'UTC'),
(now() AT TIME ZONE 'UTC')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = '';
-- Trigger on auth.users table
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
2. Profile Data Structure
Section titled “2. Profile Data Structure”The profiles table stores extended user information:
interface Profile {
id: string; // User ID (references auth.users)
name: string | null; // Display name
email: string | null; // Email address
avatar_url: string | null; // Profile picture URL
customer_id: string | null; // Stripe customer ID
price_id: string | null; // Current subscription price ID
has_access: boolean; // Access to paid features
is_admin: boolean; // Admin privileges
created_at: string; // Account creation date
updated_at: string; // Last profile update
last_login: string | null; // Last login timestamp
}
3. Updating User Profiles
Section titled “3. Updating User Profiles”// Update user profile
const updateProfile = async (updates: Partial<Profile>) => {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', user.id)
.select()
.single();
if (error) throw error;
return data;
};
// Update auth metadata
const updateAuthMetadata = async (metadata: Record<string, any>) => {
const { data, error } = await supabase.auth.updateUser({
data: metadata
});
if (error) throw error;
return data;
};
Session Management
Section titled “Session Management”1. Getting Current User
Section titled “1. Getting Current User”'use client';
import { useEffect, useState } from 'react';
import { createClient } from '@/libs/supabase/client';
function useUser() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const supabase = createClient();
useEffect(() => {
const getUser = async () => {
const { data: { user } } = await supabase.auth.getUser();
setUser(user);
setLoading(false);
};
getUser();
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user ?? null);
setLoading(false);
}
);
return () => subscription.unsubscribe();
}, []);
return { user, loading };
}
import { createClient } from '@/libs/supabase/server';
async function getServerUser() {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
return user;
}
// In a Server Component
export default async function DashboardPage() {
const user = await getServerUser();
if (!user) {
redirect('/signin');
}
return <div>Welcome, {user.email}!</div>;
}
2. Protected Routes
Section titled “2. Protected Routes”Create middleware to protect routes:
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
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, options }) => response.cookies.set(name, value, options));
},
},
}
);
const { data: { user } } = await supabase.auth.getUser();
// Protect dashboard routes
if (request.nextUrl.pathname.startsWith('/dashboard') && !user) {
return NextResponse.redirect(new URL('/signin', request.url));
}
// Protect admin routes
if (request.nextUrl.pathname.startsWith('/dashboard/admin')) {
if (!user) {
return NextResponse.redirect(new URL('/signin', request.url));
}
// Check if user is admin
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', user.id)
.single();
if (!profile?.is_admin) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return response;
}
export const config = {
matcher: ['/dashboard/:path*'],
};
Password Management
Section titled “Password Management”1. Password Reset
Section titled “1. Password Reset”// Send reset email
const resetPassword = async (email: string) => {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/update-password`,
});
if (error) throw error;
};
// Update password with token
const updatePassword = async (newPassword: string) => {
const { error } = await supabase.auth.updateUser({
password: newPassword
});
if (error) throw error;
};
2. Change Password (Authenticated User)
Section titled “2. Change Password (Authenticated User)”const changePassword = async (currentPassword: string, newPassword: string) => {
// First verify current password
const { error: signInError } = await supabase.auth.signInWithPassword({
email: user.email,
password: currentPassword,
});
if (signInError) {
throw new Error('Current password is incorrect');
}
// Update to new password
const { error } = await supabase.auth.updateUser({
password: newPassword
});
if (error) throw error;
};
Admin User Management
Section titled “Admin User Management”1. Using the useIsAdmin Hook
Section titled “1. Using the useIsAdmin Hook”// /hooks/use-admin.tsx
import { useState, useEffect } from 'react';
import { createClient } from '@/libs/supabase/client';
export function useIsAdmin() {
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
const [loading, setLoading] = useState(true);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const supabase = createClient();
const checkAdminStatus = async () => {
try {
const { data: { user: currentUser } } = await supabase.auth.getUser();
setUser(currentUser);
if (!currentUser) {
setIsAdmin(false);
setLoading(false);
return;
}
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', currentUser.id)
.single();
setIsAdmin(profile?.is_admin || false);
} catch (error) {
console.error('Error checking admin status:', error);
setIsAdmin(false);
} finally {
setLoading(false);
}
};
checkAdminStatus();
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_OUT') {
setIsAdmin(false);
setUser(null);
setLoading(false);
} else if (event === 'SIGNED_IN' && session?.user) {
setUser(session.user);
checkAdminStatus();
}
});
return () => subscription.unsubscribe();
}, []);
return { isAdmin, loading, user };
}
2. Admin Component Usage
Section titled “2. Admin Component Usage”'use client';
import { useIsAdmin } from '@/hooks/use-admin';
function AdminPanel() {
const { isAdmin, loading, user } = useIsAdmin();
if (loading) {
return <div>Loading...</div>;
}
if (!isAdmin) {
return <div>Access denied. Admin privileges required.</div>;
}
return (
<div>
<h1>Admin Dashboard</h1>
<p>Welcome, {user?.email}</p>
{/* Admin-only content */}
</div>
);
}
3. Creating Admin Users
Section titled “3. Creating Admin Users”-- Make a user admin
UPDATE profiles
SET is_admin = true
WHERE email = '[email protected]';
// In your admin user management interface
const makeUserAdmin = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ is_admin: true })
.eq('id', userId);
if (error) throw error;
};
const removeAdminAccess = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ is_admin: false })
.eq('id', userId);
if (error) throw error;
};
User Analytics and Tracking
Section titled “User Analytics and Tracking”1. Login Tracking
Section titled “1. Login Tracking”Update last login timestamp:
const updateLastLogin = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ last_login: new Date().toISOString() })
.eq('id', userId);
if (error) console.error('Error updating last login:', error);
};
2. User Statistics
Section titled “2. User Statistics”// Get user statistics
const getUserStats = async () => {
const { data, error } = await supabase
.from('profiles')
.select(`
id,
created_at,
last_login,
has_access,
is_admin
`);
if (error) throw error;
const stats = {
totalUsers: data.length,
activeUsers: data.filter(u => u.has_access).length,
adminUsers: data.filter(u => u.is_admin).length,
newUsersThisMonth: data.filter(u =>
new Date(u.created_at) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
).length,
};
return stats;
};
Security Best Practices
Section titled “Security Best Practices”1. Row Level Security (RLS)
Section titled “1. Row Level Security (RLS)”Ensure RLS policies are properly configured:
-- Users can only view/edit their own profile
CREATE POLICY "Users can view own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON profiles
FOR UPDATE USING (auth.uid() = id);
-- Admins can manage all profiles
CREATE POLICY "Admins can manage all profiles" ON profiles
FOR ALL USING (
EXISTS (
SELECT 1 FROM profiles
WHERE profiles.id = auth.uid()
AND profiles.is_admin = true
)
);
2. Rate Limiting
Section titled “2. Rate Limiting”Implement rate limiting for authentication endpoints:
// Simple rate limiting example
const rateLimiter = new Map();
const checkRateLimit = (email: string) => {
const key = `auth:${email}`;
const now = Date.now();
const limit = rateLimiter.get(key);
if (limit && now - limit.timestamp < 60000) { // 1 minute
if (limit.attempts >= 5) {
throw new Error('Too many attempts. Please try again later.');
}
limit.attempts++;
} else {
rateLimiter.set(key, { attempts: 1, timestamp: now });
}
};
3. Input Validation
Section titled “3. Input Validation”Always validate user inputs:
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validatePassword = (password: string) => {
return password.length >= 6 && password.length <= 128;
};
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Email Confirmation Not Working:
- Check email templates in Supabase dashboard
- Verify redirect URLs are correctly configured
- Check spam folder for confirmation emails
OAuth Provider Errors:
- Verify client ID and secret are correct
- Check redirect URIs match exactly
- Ensure OAuth app is published/approved
Session Not Persisting:
- Verify middleware is correctly configured
- Check cookie settings and domain
- Ensure HTTPS in production
RLS Policy Denying Access:
- Check policy conditions match your use case
- Verify user has necessary permissions
- Use service role key for admin operations
Debug Tools
Section titled “Debug Tools”Check User Session:
const debugSession = async () => {
const { data: { session } } = await supabase.auth.getSession();
console.log('Current session:', session);
const { data: { user } } = await supabase.auth.getUser();
console.log('Current user:', user);
};
Test RLS Policies:
-- Test as specific user
SELECT auth.uid(); -- Should return user ID
SELECT * FROM profiles WHERE id = auth.uid();