Skip to main content
The Bookmarks application uses Supabase Auth to provide secure user authentication with multiple sign-in methods including OAuth providers and magic link email authentication.

Authentication Architecture

The authentication system is built using Zustand for state management and Supabase Auth for the authentication backend.
The authentication logic is implemented in /src/stores/AuthStore.ts using Zustand for state management.

Key Features

OAuth Authentication

Support for multiple OAuth providers (Google, GitHub, etc.)

Magic Link (OTP)

Passwordless authentication via email

Session Management

Automatic session persistence and renewal

Secure by Default

Row Level Security ensures data isolation

Authentication Store

The application uses Zustand to manage authentication state globally:
type AuthState = {
  session: Session | null
  setSession: (session: Session | null) => void
  loading: boolean
  loginWithSocial: (provider: Provider) => Promise<void>
  loginWithOtp: (email: string) => Promise<void>
  logout: () => Promise<void>
}

Available Methods

OAuth authentication with supported providers:
const { loginWithSocial } = useAuthStore()

// Sign in with Google
await loginWithSocial('google')

// Sign in with GitHub
await loginWithSocial('github')
Implementation: /src/stores/AuthStore.ts:18-32The function:
  1. Sets loading state to true
  2. Initiates OAuth flow with signInWithOAuth
  3. Redirects to provider’s consent screen
  4. Handles callback and sets session
  5. Updates loading state

Setting Up Authentication

1

Configure Supabase

Ensure you’ve completed the Supabase Setup guide, including:
  • Enabling authentication providers
  • Configuring OAuth credentials
  • Setting redirect URLs
2

Set Environment Variables

Configure your Supabase credentials in .env.local:
VITE_SUPABASE_URL=your_project_url
VITE_SUPABASE_ANON_KEY=your_anon_key
See Environment Variables for details.
3

Initialize Supabase Client

The Supabase client is already configured in /src/utils/supabase.ts:
import { createClient } from "@supabase/supabase-js"

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

const supabase = createClient(supabaseUrl, supabaseAnonKey)

export default supabase

OAuth Provider Setup

Configuring Redirect URLs

When users sign in with OAuth, they’re redirected back to your application. The redirect URL is set automatically:
const { error } = await supabase.auth.signInWithOAuth({ 
  provider: provider,
  options: { redirectTo: window.location.origin }
})
Implementation: /src/stores/AuthStore.ts:21-23
Ensure your production domain is added to the allowed redirect URLs in Supabase Dashboard > Authentication > URL Configuration.

Supported Providers

The application supports any Supabase OAuth provider:

Google

Popular choice for most users

GitHub

Great for developer-focused apps

Microsoft

Enterprise SSO support

Apple

Required for iOS apps

Discord

Gaming communities

Twitter

Social media integration

Session Management

Checking Authentication State

Access the current session from any component:
import { useAuthStore } from '@/stores/AuthStore'

function MyComponent() {
  const session = useAuthStore((state) => state.session)
  
  if (!session) {
    return <div>Please sign in</div>
  }
  
  return <div>Welcome, {session.user.email}</div>
}

Persisting Sessions

Supabase automatically persists sessions in local storage. When users return to your app:
import { useEffect } from 'react'
import supabase from '@/utils/supabase'
import { useAuthStore } from '@/stores/AuthStore'

function App() {
  const setSession = useAuthStore((state) => state.setSession)
  
  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session)
    })
    
    // Listen for auth changes
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
    
    return () => subscription.unsubscribe()
  }, [])
  
  return <div>Your App</div>
}

Protected Routes

Ensure users are authenticated before accessing certain features:
import { Navigate } from 'react-router-dom'
import { useAuthStore } from '@/stores/AuthStore'

function ProtectedRoute({ children }) {
  const session = useAuthStore((state) => state.session)
  const loading = useAuthStore((state) => state.loading)
  
  if (loading) {
    return <div>Loading...</div>
  }
  
  if (!session) {
    return <Navigate to="/login" replace />
  }
  
  return children
}

Row Level Security

The authentication system integrates with Supabase’s Row Level Security (RLS) to ensure data isolation.
When users create bookmarks, the saved_by field is automatically set to their user ID (auth.uid()).

How It Works

  1. User signs in → Supabase creates a session with a JWT token
  2. User creates bookmark → The saved_by field is set to the user’s ID
  3. User fetches bookmarks → RLS policies filter results to only show bookmarks where saved_by matches the user’s ID
// Fetching bookmarks automatically filters by user
const { data, error } = await supabase
  .from("bookmarks")
  .select("*")
  .eq("saved_by", userId)  // User can only see their own bookmarks
Implementation: /src/stores/BookmarkStore.ts:30-36

Error Handling

The authentication store includes basic error handling:
try {
  const { error } = await supabase.auth.signInWithOAuth({ 
    provider: provider,
    options: { redirectTo: window.location.origin }
  })
  if (error) throw error
} catch (error) {
  console.log(error)
}
Implementation: /src/stores/AuthStore.ts:19-29
Consider enhancing error handling with user-friendly toast notifications using the existing toast system at /src/utils/showToast.ts.

Best Practices

Supabase automatically stores tokens securely in local storage. Never manually store tokens in cookies or local storage yourself.
Supabase handles token refresh automatically. Sessions are valid for 1 hour and refresh tokens last for 30 days by default.
Listen for SIGNED_OUT events in onAuthStateChange to handle expired sessions gracefully.
Test both OAuth and email OTP flows in development before deploying to production.

Troubleshooting

Solutions:
  • Verify redirect URLs are added in Supabase Dashboard > Authentication > URL Configuration
  • Check that OAuth credentials (Client ID/Secret) are correctly configured
  • Ensure your OAuth app’s redirect URI matches Supabase’s callback URL
Solutions:
  • Ensure local storage is enabled in the browser
  • Check for conflicting auth logic that might clear sessions
  • Verify onAuthStateChange listener is properly set up
Solutions:
  • Verify Row Level Security is enabled on the bookmarks table
  • Check that RLS policies are correctly configured
  • Ensure saved_by field matches auth.uid() in queries

Next Steps

Deploy to Production

Deploy your application with authentication

Supabase Setup

Review complete Supabase configuration

Build docs developers (and LLMs) love