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:
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 ,
},
}
);
The client includes fallback values for local development, so you can start coding without configuration.
Email Authentication
Email/password authentication is enabled by default in Supabase.
Configuration
Local Development
No additional configuration needed. Supabase local instance has email auth enabled.
Production
Configure email settings in Supabase Dashboard:
Navigate to Authentication → Settings
Configure Email Templates (optional)
Set up SMTP settings (optional, uses Supabase’s SMTP by default)
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
packages/app/supabase/config.toml
[ 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
Create Google OAuth Credentials
Go to Google Cloud Console
Create a new project or select existing
Enable Google+ API
Navigate to Credentials → Create Credentials → OAuth 2.0 Client ID
Set Application Type to “Web application”
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
Add Credentials to Environment
Add to packages/landing/.env.local: GOOGLE_CLIENT_ID = 123456789-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET = GOCSPX-your-secret-here
Configure Supabase
Local development (packages/app/supabase/config.toml):[ auth . external . google ]
enabled = true
client_id = "your-client-id"
secret = "your-client-secret"
Production (Supabase Dashboard):
Navigate to Authentication → Providers
Enable Google
Add Client ID and Client Secret
Never commit GOOGLE_CLIENT_SECRET to version control. Use environment variables or secret management.
Profile Creation
Automatic Profile Handling
When a user signs up, a trigger automatically creates their profile:
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:
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:
{
auth : {
persistSession : true , // Store session in localStorage
autoRefreshToken : true , // Auto-refresh before expiry
detectSessionInUrl : true , // Handle OAuth callbacks
}
}
Getting Current User
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
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:
-- 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:
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:
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):
Sign In Flow
Email/Password
Google OAuth
Sign Out
const { data , error } = await supabase . auth . signInWithPassword ({
email: '[email protected] ' ,
password: 'password123' ,
});
if ( error ) {
console . error ( 'Sign in failed:' , error . message );
} else {
console . log ( 'Signed in:' , data . user );
}
const { data , error } = await supabase . auth . signInWithOAuth ({
provider: 'google' ,
options: {
redirectTo: window . location . origin + '/auth/callback' ,
},
});
if ( error ) {
console . error ( 'OAuth failed:' , error . message );
}
const { error } = await supabase . auth . signOut ();
if ( error ) {
console . error ( 'Sign out failed:' , error . message );
} else {
console . log ( 'Signed out successfully' );
}
Security Best Practices
Environment Variables Store OAuth secrets in environment variables, never in code
HTTPS Only Always use HTTPS in production for OAuth callbacks
Row Level Security Enable RLS on all tables to enforce access control
Token Refresh Enable auto-refresh to prevent session expiration
Troubleshooting
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).
Check that persistSession: true is set in client configuration. Clear localStorage and try again: localStorage . clear ();
window . location . reload ();
Profile not created automatically
Check that the trigger exists: select * from pg_trigger where tgname = 'on_auth_user_created' ;
Re-run migrations if missing: cd packages/app
supabase db reset
Additional Providers
Supabase supports many OAuth providers. To add more:
Enable provider in Supabase Dashboard or config.toml
Add credentials to environment variables
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
Database Setup Understand the profile and user schema
Environment Variables Configure all required variables