Authentication Patterns
The Tuturuuu platform uses Supabase Auth for user authentication with support for email/password, OAuth providers, multi-factor authentication (MFA), and cross-app token authentication.Authentication Flow
Copy
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │──1──>│ Supabase │──2──>│ Database │
│ (Browser/App)│ │ Auth │ │ (RLS) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
│<─────────3───────────│ │
│ Session Token │ │
│ │ │
│──────4──────────────────────────────────────>│
│ Authenticated Request (w/ session) │
│<─────────────────────────────────────────────│
│ Data (filtered by RLS) │
- User signs in via Supabase Auth
- Supabase validates credentials against database
- Client receives session token
- Subsequent requests include session token for RLS
Sign Up
Basic Email/Password Sign Up
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
import { redirect } from 'next/navigation';
export async function signUp(formData: FormData) {
const supabase = createClient();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const displayName = formData.get('displayName') as string;
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
display_name: displayName,
},
emailRedirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
},
});
if (error) {
return { error: error.message };
}
redirect('/verify-email');
}
Client Component
Copy
'use client';
import { signUp } from './actions';
import { useState } from 'react';
export function SignUpForm() {
const [error, setError] = useState<string | null>(null);
async function handleSubmit(formData: FormData) {
const result = await signUp(formData);
if (result?.error) {
setError(result.error);
}
}
return (
<form action={handleSubmit}>
<input name="displayName" placeholder="Display Name" required />
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
{error && <p className="text-dynamic-red">{error}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
Sign In
Email/Password Sign In
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
import { redirect } from 'next/navigation';
export async function signIn(formData: FormData) {
const supabase = createClient();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return { error: error.message };
}
redirect('/dashboard');
}
OAuth Sign In
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function signInWithOAuth(provider: 'google' | 'github') {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
},
});
if (error) {
return { error: error.message };
}
return { url: data.url };
}
Copy
'use client';
import { signInWithOAuth } from './actions';
export function OAuthButtons() {
async function handleOAuthSignIn(provider: 'google' | 'github') {
const result = await signInWithOAuth(provider);
if (result.url) {
window.location.href = result.url;
}
}
return (
<div>
<button onClick={() => handleOAuthSignIn('google')}>
Sign in with Google
</button>
<button onClick={() => handleOAuthSignIn('github')}>
Sign in with GitHub
</button>
</div>
);
}
Sign Out
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
import { redirect } from 'next/navigation';
export async function signOut() {
const supabase = createClient();
await supabase.auth.signOut();
redirect('/login');
}
Multi-Factor Authentication (MFA)
Enable TOTP MFA
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function enableMFA() {
const supabase = createClient();
// Enroll in MFA
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
});
if (error) throw error;
return {
qrCode: data.totp.qr_code, // Display to user
secret: data.totp.secret, // For manual entry
factorId: data.id,
};
}
Verify MFA Enrollment
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function verifyMFAEnrollment(factorId: string, code: string) {
const supabase = createClient();
const { data, error } = await supabase.auth.mfa.challengeAndVerify({
factorId,
code,
});
if (error) throw error;
return { success: true };
}
MFA Challenge During Sign In
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function signInWithMFA(email: string, password: string) {
const supabase = createClient();
// Initial sign in
const { data: signInData, error: signInError } =
await supabase.auth.signInWithPassword({
email,
password,
});
if (signInError) throw signInError;
// Check if MFA is required
const { data: factors } = await supabase.auth.mfa.listFactors();
if (factors && factors.totp.length > 0) {
const factorId = factors.totp[0].id;
// Create MFA challenge
const { data: challengeData, error: challengeError } =
await supabase.auth.mfa.challenge({ factorId });
if (challengeError) throw challengeError;
return {
requiresMFA: true,
challengeId: challengeData.id,
factorId,
};
}
return { requiresMFA: false };
}
Verify MFA Code
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function verifyMFACode(
factorId: string,
challengeId: string,
code: string
) {
const supabase = createClient();
const { data, error } = await supabase.auth.mfa.verify({
factorId,
challengeId,
code,
});
if (error) throw error;
return { success: true };
}
Session Management
Get Current Session
Copy
import { createClient } from '@tuturuuu/supabase/server';
export async function getSession() {
const supabase = createClient();
const { data: { session }, error } = await supabase.auth.getSession();
if (error) throw error;
return session;
}
Get Current User
Copy
import { createClient } from '@tuturuuu/supabase/server';
export async function getCurrentUser() {
const supabase = createClient();
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
return null;
}
return user;
}
Refresh Session
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function refreshSession() {
const supabase = createClient();
const { data, error } = await supabase.auth.refreshSession();
if (error) throw error;
return data.session;
}
Password Reset
Request Password Reset
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function requestPasswordReset(email: string) {
const supabase = createClient();
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/auth/reset-password`,
});
if (error) throw error;
return { success: true };
}
Reset Password
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function resetPassword(newPassword: string) {
const supabase = createClient();
const { data, error } = await supabase.auth.updateUser({
password: newPassword,
});
if (error) throw error;
return { success: true };
}
Email Verification
Resend Verification Email
Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function resendVerificationEmail() {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user?.email) {
throw new Error('No user email found');
}
const { error } = await supabase.auth.resend({
type: 'signup',
email: user.email,
});
if (error) throw error;
return { success: true };
}
Cross-App Authentication
The platform supports token-based authentication across different apps using@tuturuuu/auth/cross-app
.
Generate Cross-App Token
Copy
import { generateCrossAppToken } from '@tuturuuu/auth/cross-app';
export async function createShortenerLink(userId: string) {
// Generate token that shortener app can verify
const token = await generateCrossAppToken(userId, {
expiresIn: '1h',
app: 'shortener',
});
return {
url: `${process.env.SHORTENER_APP_URL}/create?token=${token}`,
};
}
Verify Cross-App Token
Copy
import { verifyCrossAppToken } from '@tuturuuu/auth/cross-app';
export async function handleCrossAppRequest(token: string) {
const payload = await verifyCrossAppToken(token);
if (!payload) {
throw new Error('Invalid token');
}
// payload contains: { userId, app, exp, iat }
return payload;
}
Middleware Authentication
Protect routes using Next.js middleware:Copy
// middleware.ts
import { createDynamicClient } from '@tuturuuu/supabase/server';
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(request: NextRequest) {
const supabase = createDynamicClient();
const { data: { user } } = await supabase.auth.getUser();
// Protect dashboard routes
if (request.nextUrl.pathname.startsWith('/dashboard') && !user) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Redirect authenticated users from auth pages
if (request.nextUrl.pathname.startsWith('/login') && user) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login', '/signup'],
};
Protected Server Component
Copy
import { createClient } from '@tuturuuu/supabase/server';
import { redirect } from 'next/navigation';
export default async function ProtectedPage() {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
redirect('/login');
}
return (
<div>
<h1>Welcome, {user.email}</h1>
</div>
);
}
Client-Side Authentication
useUser Hook
Copy
'use client';
import { createClient } from '@tuturuuu/supabase/client';
import { useEffect, useState } from 'react';
import type { User } from '@supabase/supabase-js';
export function useUser() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const supabase = createClient();
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
setLoading(false);
});
// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => subscription.unsubscribe();
}, []);
return { user, loading };
}
Usage
Copy
'use client';
import { useUser } from '@/hooks/useUser';
export function UserProfile() {
const { user, loading } = useUser();
if (loading) return <div>Loading...</div>;
if (!user) return <div>Not authenticated</div>;
return <div>Logged in as {user.email}</div>;
}
Identity Linking
Link multiple auth providers to same account:Copy
'use server';
import { createClient } from '@tuturuuu/supabase/server';
export async function linkIdentity(provider: 'google' | 'github') {
const supabase = createClient();
const { data, error } = await supabase.auth.linkIdentity({
provider,
});
if (error) throw error;
return { url: data.url };
}
Best Practices
✅ DO
-
Always check authentication server-side
Copy
const { data: { user } } = await supabase.auth.getUser(); if (!user) throw new Error('Unauthorized');
-
Use edge runtime for auth routes
Copy
// app/api/auth/otp/route.ts export const runtime = 'edge';
-
Redirect after authentication
Copy
redirect('/dashboard'); // Don't return sensitive data
-
Handle errors gracefully
Copy
if (error) return { error: error.message };
-
Implement MFA for sensitive operations
Copy
const mfaRequired = await checkMFAEnabled(userId);
❌ DON’T
-
Don’t trust client-side auth state alone
Copy
// ❌ Bad if (localStorage.getItem('user')) { /* ... */ }
-
Don’t expose sensitive data in auth redirects
Copy
// ❌ Bad redirect(`/dashboard?apiKey=${apiKey}`);
-
Don’t store passwords
Copy
// ❌ Bad: Let Supabase handle password storage
-
Don’t use
createAdminClient()
for auth operationsCopy// ❌ Bad: Use regular client const supabase = createAdminClient(); await supabase.auth.signUp({ ... });
Related Documentation
- Authorization - Permission system
- Supabase Client - Client creation patterns
- RLS Policies - Database security