Skip to main content
8Space uses Supabase Auth for user authentication. The app supports email/password signup, password login, and Google OAuth.

Authentication Methods

Email/Password Signup

Create a new user account with email and password. Endpoint: POST /auth/v1/signup
email
string
required
User’s email address (must be valid email format)
password
string
required
User’s password (minimum 6 characters)
options.data.name
string
User’s display name (stored in user metadata)
Example Request:
const { error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'secure_password',
  options: {
    data: {
      name: 'John Doe',
    },
  },
});

if (error) {
  throw error;
}
Response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "refreshTokenString",
  "expires_in": 3600,
  "token_type": "bearer",
  "user": {
    "id": "9b7f3c6e-8e4a-4d3b-9f1a-2c5e6b8d7a9f",
    "email": "[email protected]",
    "user_metadata": {
      "name": "John Doe"
    },
    "app_metadata": {}
  }
}
The signup method is implemented in use-auth.tsx:155-168 as signUpWithPassword.

Password Login

Sign in with existing credentials. Endpoint: POST /auth/v1/token?grant_type=password
grant_type
string
required
Must be password for password grant flow
email
string
required
User’s email address
password
string
required
User’s password
Example Request:
const { error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'secure_password',
});

if (error) {
  throw error;
}
Response:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "refreshTokenString",
  "user": {
    "id": "9b7f3c6e-8e4a-4d3b-9f1a-2c5e6b8d7a9f",
    "email": "[email protected]"
  }
}
The password sign-in method is implemented in use-auth.tsx:149-153 as signInWithPassword.

Google OAuth

Sign in with Google OAuth provider. Endpoint: GET /auth/v1/authorize?provider=google
provider
string
required
OAuth provider, must be google
redirect_to
string
Callback URL after OAuth completes
Example Request:
const callbackUrl = new URL(
  `${import.meta.env.BASE_URL}auth/callback`,
  window.location.origin
).toString();

const { error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: callbackUrl,
  },
});

if (error) {
  throw error;
}
Response: 302 redirect to Google OAuth consent screen
The Google OAuth method is implemented in use-auth.tsx:170-180 as signInWithGoogle.

Auth Callback

After OAuth completes, Google redirects to the callback route. Endpoint: GET /auth/callback
code
string
Supabase auth code to exchange for session
next
string
default:"/app"
Relative redirect path after successful exchange
Response: 302 redirect to next parameter (default /app)
The callback route exchanges the OAuth code for a session and sets cookies before redirecting the user.

Session Management

Get Current User

Retrieve the authenticated user’s information. Endpoint: GET /auth/v1/user Headers:
apikey
string
required
Supabase anon/service key
Authorization
string
required
Bearer token: Bearer {access_token}
Response:
{
  "id": "9b7f3c6e-8e4a-4d3b-9f1a-2c5e6b8d7a9f",
  "email": "[email protected]",
  "user_metadata": {
    "name": "John Doe"
  },
  "app_metadata": {}
}

Sign Out

Log out the current user session. Endpoint: POST /auth/v1/logout Headers:
apikey
string
required
Supabase anon/service key
Authorization
string
required
Bearer token: Bearer {access_token}
Example Request:
const { error } = await supabase.auth.signOut();

if (error) {
  // Fallback to local sign out if remote fails
  const { error: localError } = await supabase.auth.signOut({ scope: 'local' });
  if (localError) {
    throw localError;
  }
}
Response: 200 with empty body
The sign-out method is implemented in use-auth.tsx:182-189 with a fallback to local sign-out if the remote call fails.

Auth Context

8Space provides a React context for managing authentication state throughout the app.

AuthProvider

Wrap your app with AuthProvider to provide auth context:
import { AuthProvider } from '@/hooks/use-auth';

function App() {
  return (
    <AuthProvider>
      {/* Your app components */}
    </AuthProvider>
  );
}

useAuth Hook

Access authentication state and methods:
import { useAuth } from '@/hooks/use-auth';

function MyComponent() {
  const {
    session,      // Current session object
    user,         // Current user object
    profile,      // User profile with display_name and avatar_url
    loading,      // Loading state during session bootstrap
    signInWithPassword,
    signUpWithPassword,
    signInWithGoogle,
    signOut,
  } = useAuth();

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!user) {
    return <div>Not authenticated</div>;
  }

  return <div>Welcome, {profile?.displayName}!</div>;
}
Context Values:
session
Session | null
Current Supabase session object
user
User | null
Current authenticated user
profile
UserProfile | null
User profile with id, displayName, and avatarUrl
loading
boolean
True during initial session bootstrap (max 15 seconds)
signInWithPassword
function
(email: string, password: string) => Promise<void>
signUpWithPassword
function
(email: string, password: string, name: string) => Promise<void>
signInWithGoogle
function
() => Promise<void>
signOut
function
() => Promise<void>

Session Bootstrap

The AuthProvider automatically restores sessions on mount:
const { data, error } = await supabase.auth.getSession();

if (data.session?.user?.id) {
  // Fetch user profile
  const { data: profile } = await supabase
    .from('profiles')
    .select('id,display_name,avatar_url')
    .eq('id', data.session.user.id)
    .maybeSingle();
}
Session bootstrap has a 15-second timeout. If bootstrap takes longer, the local session is cleared and loading completes.
Bootstrap Process:
  1. Call supabase.auth.getSession() to restore session from cookies/localStorage
  2. If session exists, fetch user profile from profiles table
  3. Set session, user, and profile in context
  4. Set loading to false
  5. Subscribe to auth state changes
Timeout Handling:
const loadingFallbackTimer = window.setTimeout(() => {
  console.warn('Auth bootstrap timed out, clearing stale local session.');
  void supabase.auth.signOut({ scope: 'local' });
  setSession(null);
  setUser(null);
  setProfile(null);
  setLoading(false);
}, 15000);
The bootstrap logic is implemented in use-auth.tsx:74-113.

Auth State Changes

The AuthProvider listens for auth state changes and updates context:
const authSubscription = supabase.auth.onAuthStateChange(
  async (_event, nextSession) => {
    setSession(nextSession);
    setUser(nextSession?.user ?? null);

    if (nextSession?.user?.id) {
      const profileData = await fetchProfile(nextSession.user.id);
      setProfile(profileData);
    } else {
      setProfile(null);
    }
  }
);
Auth state change listener is implemented in use-auth.tsx:115-132.

Profile Fetching

User profiles are fetched from the profiles table:
async function fetchProfile(userId: string): Promise<UserProfile | null> {
  const { data, error } = await supabase
    .from('profiles')
    .select('id,display_name,avatar_url')
    .eq('id', userId)
    .maybeSingle();

  if (error) {
    throw error;
  }

  if (!data) {
    return null;
  }

  return {
    id: data.id,
    displayName: data.display_name,
    avatarUrl: data.avatar_url,
  };
}
The fetchProfile function is implemented in use-auth.tsx:21-41.

Making Authenticated Requests

All Supabase Data and RPC endpoints require authentication: Required Headers:
apikey
string
required
Supabase anon key from project settings
Authorization
string
required
Bearer token with format: Bearer {access_token}
Example:
// The Supabase client automatically adds these headers
const { data, error } = await supabase
  .from('tasks')
  .select('*')
  .eq('project_id', projectId);
Row Level Security (RLS) policies enforce access control. A 403 response indicates the user lacks permission for the requested resource.

Error Handling

Common Error Codes:
400
Bad Request
Invalid credentials or validation error
401
Unauthorized
Missing or invalid authentication token
403
Forbidden
User lacks permission (RLS policy denied access)
Error Response Format:
{
  "message": "Invalid login credentials",
  "code": "invalid_credentials"
}

Security Best Practices

Never expose the Supabase service role key in client-side code. Only use the anon key.
  1. Use Row Level Security (RLS): Enable RLS policies on all tables to enforce access control
  2. Validate inputs: Always validate user inputs before sending to the API
  3. Handle token refresh: Supabase automatically refreshes tokens, but handle errors gracefully
  4. Secure redirects: Validate redirect URLs to prevent open redirect vulnerabilities
  5. Use HTTPS: Always use HTTPS in production to protect tokens in transit

Next Steps

API Overview

Explore all available API endpoints

Build docs developers (and LLMs) love