Authentication API
Estudio Three uses Supabase Auth for user authentication and session management. All authentication operations are handled through the useAuthStore Zustand store.
Store Location
src/stores/useAuthStore.ts
Data Types
AuthProfile
interface AuthProfile {
id: string; // User UUID (matches auth.users.id)
email: string; // User email
username?: string | null; // Unique username (min 3 chars)
full_name: string | null; // Display name
avatar_url: string | null; // Profile picture URL
date_of_birth: string | null; // ISO date string
auth_provider: string; // 'email', 'google', 'github'
has_onboarded: boolean; // Onboarding completion flag
created_at: string; // ISO timestamp
}
SocialProvider
type SocialProvider = 'google' | 'github';
Authentication Methods
Initialize Session
Call this on app startup to restore the user’s session:
import { useAuthStore } from '@/stores/useAuthStore';
// Initialize authentication
await useAuthStore.getState().initialize();
What it does:
- Retrieves current session from Supabase
- Fetches user profile with retry logic (3 attempts)
- Sets up
onAuthStateChange listener
- Handles OAuth profile creation
Sign Up with Email
Password (Supabase default: minimum 6 characters)
User’s full name for display
Unique username (minimum 3 characters)
await useAuthStore.getState().signUpWithEmail(
'[email protected]',
'secure_password',
'John Doe',
'johndoe'
);
Process:
- Validates and sanitizes username
- Checks username uniqueness
- Creates Supabase auth user
- Sends verification email
- Sets
needsEmailVerification flag
Errors:
auth.errors.usernameInvalid: Username doesn’t meet requirements
auth.errors.usernameTaken: Username already exists
Sign In with Email
await useAuthStore.getState().signInWithEmail(
'[email protected]',
'secure_password'
);
Process:
- Authenticates with Supabase
- Checks email verification status
- Forces sign-out if email not verified
- Fetches user profile
Errors:
auth.errors.verifyEmail: Email not verified (user signed out)
Sign In with Username or Email
Email address or username
await useAuthStore.getState().signInWithEmailOrUsername(
'johndoe', // Can also be '[email protected]'
'secure_password'
);
Process:
- Detects if login is email (contains ’@’) or username
- If username, queries
profiles table for email
- Proceeds with email-based authentication
Errors:
auth.errors.userNotFound: Username doesn’t exist
OAuth Sign In
OAuth provider: ‘google’ or ‘github’
await useAuthStore.getState().signInWithProvider('google');
Configuration:
- Google: Requests offline access with consent prompt
- Redirect URL: Returns to app origin (
window.location.origin)
- Profile Creation: Automatically creates profile from OAuth metadata
OAuth Profile Mapping:
{
full_name: meta.full_name || meta.name || meta.user_name,
avatar_url: meta.avatar_url || meta.picture,
username: meta.preferred_username || meta.user_name,
auth_provider: user.app_metadata.provider
}
Sign Out
await useAuthStore.getState().signOut();
Clears session and resets store state.
Password Management
Reset Password Request
Process:
- Sends password reset email
- Sets
emailSent flag
- Redirect URL:
/auth/reset-password
Update Password
New password (minimum 6 characters)
await useAuthStore.getState().updatePassword('new_secure_password');
Profile Management
Update Profile
updates
Partial<AuthProfile>
required
Fields to update
await useAuthStore.getState().updateProfile({
full_name: 'Jane Doe',
username: 'janedoe',
avatar_url: 'https://example.com/avatar.jpg'
});
Username Validation:
- Sanitized before saving
- Uniqueness checked (excluding current user)
- Error thrown if username taken
Update Date of Birth
ISO date string (YYYY-MM-DD)
await useAuthStore.getState().updateDateOfBirth('2000-01-15');
Email Verification
Resend Verification Email
await useAuthStore.getState().resendVerificationEmail();
Requirements:
- User must be signed in
- Sets
emailSent flag on success
Check Verification Status
const { needsEmailVerification } = useAuthStore.getState();
Store State
Access Current User
const { user, profile, isLoading, error } = useAuthStore();
if (user) {
console.log('Authenticated:', user.email);
console.log('Profile:', profile);
}
Store Fields
Supabase auth user object
Extended user profile from database
Loading state for async operations
Error message from last operation
Flag indicating verification/reset email sent
True if user email not confirmed
Session Persistence
The auth store is persisted to localStorage:
{
name: 'auth-storage',
storage: localStorage,
partialize: (state) => ({
user: state.user,
profile: state.profile
})
}
Note: Only user and profile are persisted. Flags like isLoading and error are reset on page load.
Row Level Security
The profiles table has these RLS policies:
-- Anyone can view profiles (for social features)
CREATE POLICY "Perfiles públicos visibles por todos"
ON profiles FOR SELECT
USING (true);
-- Users can insert their own profile
CREATE POLICY "Usuarios pueden insertar su perfil"
ON profiles FOR INSERT
WITH CHECK (auth.uid() = id);
-- Users can update their own profile
CREATE POLICY "Usuarios pueden actualizar su perfil"
ON profiles FOR UPDATE
USING (auth.uid() = id);
Auto-Logout Feature
If session becomes invalid (e.g., expired token), the store automatically:
- Clears persisted state
- Sets
user and profile to null
- Redirects user to login (handled by route guards)
Error Handling
Clear Error State
useAuthStore.getState().clearError();
Clear Email Sent Flag
useAuthStore.getState().clearEmailSent();
Example: Protected Route
import { useAuthStore } from '@/stores/useAuthStore';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
function ProtectedPage() {
const { user, profile } = useAuthStore();
const navigate = useNavigate();
useEffect(() => {
if (!user) {
navigate('/auth/signin');
}
}, [user, navigate]);
if (!profile?.has_onboarded) {
return <div>Complete onboarding first</div>;
}
return <div>Welcome, {profile.full_name}!</div>;
}
Database Schema
From /home/daytona/workspace/source/supabase/schema.sql:30:
create table profiles (
id uuid references auth.users not null primary key,
updated_at timestamp with time zone,
username text unique,
full_name text,
avatar_url text,
website text,
sport_info jsonb default '{}'::jsonb,
preferences jsonb default '{}'::jsonb,
has_onboarded boolean default false,
constraint username_length check (char_length(username) >= 3)
);