> ## Documentation Index
> Fetch the complete documentation index at: https://docs.8space.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Configure Supabase Authentication with email and social providers

8Space uses Supabase Auth for secure authentication with support for email/password and OAuth providers.

## Overview

Authentication features:

* Email/password authentication
* Google OAuth integration
* Automatic profile creation
* Session persistence
* Auto-refresh tokens
* Row Level Security (RLS) integration

## Supabase Client Configuration

The authentication client is configured in `packages/app/src/integrations/supabase/client.ts`:

```typescript theme={null}
import { createClient } from '@supabase/supabase-js';

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

export const supabase = createClient(
  supabaseUrl ?? fallbackUrl,
  supabaseAnonKey ?? fallbackAnonKey,
  {
    auth: {
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: true,
    },
  }
);
```

<Info>
  The client includes fallback values for local development, so you can start coding without configuration.
</Info>

## Email Authentication

Email/password authentication is enabled by default in Supabase.

### Configuration

<Steps>
  <Step title="Local Development">
    No additional configuration needed. Supabase local instance has email auth enabled.
  </Step>

  <Step title="Production">
    Configure email settings in Supabase Dashboard:

    1. Navigate to Authentication → Settings
    2. Configure Email Templates (optional)
    3. Set up SMTP settings (optional, uses Supabase's SMTP by default)
  </Step>
</Steps>

### Email Verification

By default, Supabase requires email verification. You can disable this in:

* **Local**: `packages/app/supabase/config.toml`
* **Production**: Supabase Dashboard → Authentication → Settings → Email Auth

```toml packages/app/supabase/config.toml theme={null}
[auth.email]
enable_signup = true
double_confirm_changes = true
enable_confirmations = false  # Set to false to disable email verification
```

## Google OAuth

Google OAuth allows users to sign in with their Google account.

### Setup Instructions

<Steps>
  <Step title="Create Google OAuth Credentials">
    1. Go to [Google Cloud Console](https://console.cloud.google.com)
    2. Create a new project or select existing
    3. Enable Google+ API
    4. Navigate to Credentials → Create Credentials → OAuth 2.0 Client ID
    5. Set Application Type to "Web application"
  </Step>

  <Step title="Configure Redirect URIs">
    Add authorized redirect URIs:

    **Local development:**

    ```
    http://127.0.0.1:54321/auth/v1/callback
    ```

    **Production:**

    ```
    https://your-project.supabase.co/auth/v1/callback
    ```
  </Step>

  <Step title="Add Credentials to Environment">
    Add to `packages/landing/.env.local`:

    ```bash theme={null}
    GOOGLE_CLIENT_ID=123456789-abc.apps.googleusercontent.com
    GOOGLE_CLIENT_SECRET=GOCSPX-your-secret-here
    ```
  </Step>

  <Step title="Configure Supabase">
    **Local development** (`packages/app/supabase/config.toml`):

    ```toml theme={null}
    [auth.external.google]
    enabled = true
    client_id = "your-client-id"
    secret = "your-client-secret"
    ```

    **Production** (Supabase Dashboard):

    1. Navigate to Authentication → Providers
    2. Enable Google
    3. Add Client ID and Client Secret
  </Step>
</Steps>

<Warning>
  Never commit `GOOGLE_CLIENT_SECRET` to version control. Use environment variables or secret management.
</Warning>

## Profile Creation

### Automatic Profile Handling

When a user signs up, a trigger automatically creates their profile:

```sql theme={null}
create or replace function public.handle_new_user()
returns trigger
language plpgsql
security definer
set search_path = public
as $$
begin
  insert into public.profiles (id, display_name, avatar_url)
  values (
    new.id,
    coalesce(
      new.raw_user_meta_data ->> 'full_name',
      new.raw_user_meta_data ->> 'name',
      split_part(new.email, '@', 1)
    ),
    coalesce(
      new.raw_user_meta_data ->> 'avatar_url',
      new.raw_user_meta_data ->> 'picture'
    )
  )
  on conflict (id) do update set
    display_name = coalesce(excluded.display_name, profiles.display_name),
    avatar_url = coalesce(excluded.avatar_url, profiles.avatar_url);

  return new;
end;
$$;
```

This trigger:

* Extracts name from OAuth metadata (supports Google)
* Falls back to email prefix if no name provided
* Handles both `avatar_url` and `picture` fields
* Updates existing profiles on conflict

### Profile Fields

The `profiles` table stores user information:

```sql theme={null}
create table public.profiles (
  id uuid primary key references auth.users (id) on delete cascade,
  display_name text not null default 'User',
  avatar_url text,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);
```

## Session Management

### Session Persistence

Sessions are automatically persisted to localStorage:

```typescript theme={null}
{
  auth: {
    persistSession: true,  // Store session in localStorage
    autoRefreshToken: true, // Auto-refresh before expiry
    detectSessionInUrl: true, // Handle OAuth callbacks
  }
}
```

### Getting Current User

```typescript theme={null}
const { data: { user } } = await supabase.auth.getUser();

if (user) {
  console.log('User ID:', user.id);
  console.log('Email:', user.email);
  console.log('Metadata:', user.user_metadata);
}
```

### Auth State Changes

```typescript theme={null}
supabase.auth.onAuthStateChange((event, session) => {
  if (event === 'SIGNED_IN') {
    console.log('User signed in:', session.user);
  }
  if (event === 'SIGNED_OUT') {
    console.log('User signed out');
  }
});
```

## Row Level Security

### Auth Integration

RLS policies use `auth.uid()` to enforce permissions:

```sql theme={null}
-- Users can only read their own profile
create policy "profiles_select_authenticated"
on public.profiles for select to authenticated
using (true);

-- Users can only update their own profile
create policy "profiles_update_self"
on public.profiles for update to authenticated
using (id = auth.uid())
with check (id = auth.uid());
```

### Tenant Access

Tenant access is controlled via membership:

```sql theme={null}
create policy "tenants_select_members"
on public.tenants for select to authenticated
using (public.is_tenant_member(id));
```

The `is_tenant_member()` function checks the current user's tenant membership:

```sql theme={null}
create or replace function public.is_tenant_member(p_tenant_id uuid)
returns boolean
language sql
stable
security definer
as $$
  select exists(
    select 1 from public.tenant_members
    where tenant_id = p_tenant_id
      and user_id = auth.uid()
  );
$$;
```

## Testing Authentication

### Local Test Accounts

The seed file creates test users (password: `password123`):

* **[owner@gantt.local](mailto:owner@gantt.local)** - Tenant owner role
* **[editor@gantt.local](mailto:editor@gantt.local)** - Tenant admin role
* **[viewer@gantt.local](mailto:viewer@gantt.local)** - Tenant member role

### Sign In Flow

<Tabs>
  <Tab title="Email/Password">
    ```typescript theme={null}
    const { data, error } = await supabase.auth.signInWithPassword({
      email: 'owner@gantt.local',
      password: 'password123',
    });

    if (error) {
      console.error('Sign in failed:', error.message);
    } else {
      console.log('Signed in:', data.user);
    }
    ```
  </Tab>

  <Tab title="Google OAuth">
    ```typescript theme={null}
    const { data, error } = await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: window.location.origin + '/auth/callback',
      },
    });

    if (error) {
      console.error('OAuth failed:', error.message);
    }
    ```
  </Tab>

  <Tab title="Sign Out">
    ```typescript theme={null}
    const { error } = await supabase.auth.signOut();

    if (error) {
      console.error('Sign out failed:', error.message);
    } else {
      console.log('Signed out successfully');
    }
    ```
  </Tab>
</Tabs>

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Environment Variables" icon="key">
    Store OAuth secrets in environment variables, never in code
  </Card>

  <Card title="HTTPS Only" icon="lock">
    Always use HTTPS in production for OAuth callbacks
  </Card>

  <Card title="Row Level Security" icon="shield">
    Enable RLS on all tables to enforce access control
  </Card>

  <Card title="Token Refresh" icon="rotate">
    Enable auto-refresh to prevent session expiration
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Google OAuth not working locally">
    Ensure redirect URI matches exactly:

    ```
    http://127.0.0.1:54321/auth/v1/callback
    ```

    Note: Use `127.0.0.1`, not `localhost` (Google treats them differently).
  </Accordion>

  <Accordion title="Session not persisting">
    Check that `persistSession: true` is set in client configuration. Clear localStorage and try again:

    ```typescript theme={null}
    localStorage.clear();
    window.location.reload();
    ```
  </Accordion>

  <Accordion title="Profile not created automatically">
    Check that the trigger exists:

    ```sql theme={null}
    select * from pg_trigger where tgname = 'on_auth_user_created';
    ```

    Re-run migrations if missing:

    ```bash theme={null}
    cd packages/app
    supabase db reset
    ```
  </Accordion>
</AccordionGroup>

## Additional Providers

Supabase supports many OAuth providers. To add more:

1. Enable provider in Supabase Dashboard or `config.toml`
2. Add credentials to environment variables
3. Update the `handle_new_user()` function if provider uses different metadata fields

**Supported providers:**

* GitHub
* GitLab
* Bitbucket
* Azure
* Facebook
* Twitter
* Discord
* And more...

## Next Steps

<CardGroup cols={2}>
  <Card title="Database Setup" icon="database" href="/configuration/database-setup">
    Understand the profile and user schema
  </Card>

  <Card title="Environment Variables" icon="key" href="/configuration/environment-variables">
    Configure all required variables
  </Card>
</CardGroup>
