Overview
Trending Society uses Supabase Auth for authentication across all products. One login works everywhere: Dashboard, Publisher, Platform, Agency, and Store.
Authentication is handled by the @trendingsociety/auth package. See Packages for details.
Supported Providers
Email/Password Traditional email signup with confirmation
Google OAuth One-click Google login
Magic Link Passwordless email login
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ User Login │
│ ↓ │
│ Supabase Auth (auth.users) │
│ ↓ │
│ JWT Token (contains user_id, tenant_id) │
│ ↓ │
│ RLS Policies (filter data by tenant_id) │
│ ↓ │
│ User sees only their tenant's data │
│ │
└─────────────────────────────────────────────────────────────────┘
Implementation
Server-Side (App Router)
import { createServerClient } from '@trendingsociety/auth' ;
import { cookies } from 'next/headers' ;
export async function getUser () {
const cookieStore = await cookies ();
const supabase = createServerClient ( cookieStore );
const { data : { user } } = await supabase . auth . getUser ();
return user ;
}
Client-Side
import { useAuth } from '@trendingsociety/auth' ;
export function LoginButton () {
const { signIn , signOut , user } = useAuth ();
if ( user ) {
return < button onClick ={ signOut }> Sign Out </ button > ;
}
return < button onClick ={() => signIn ( 'google' )}> Sign in with Google </ button > ;
}
Middleware Protection
// middleware.ts
import { createMiddlewareClient } from '@trendingsociety/auth' ;
import { NextResponse } from 'next/server' ;
export async function middleware ( request ) {
const supabase = createMiddlewareClient ( request );
const { data : { session } } = await supabase . auth . getSession ();
if ( ! session && request . nextUrl . pathname . startsWith ( '/dashboard' )) {
return NextResponse . redirect ( new URL ( '/login' , request . url ));
}
return NextResponse . next ();
}
Multi-Tenant Context
After authentication, users are associated with a tenant :
import { getTenantId } from '@/lib/tenant/server' ;
export async function getData () {
const tenantId = await getTenantId ();
// All queries automatically filtered by tenant_id via RLS
const { data } = await supabase
. from ( 'content' )
. select ( '*' );
return data ; // Only returns current tenant's data
}
Never bypass RLS in user-facing code. Use service_role only in Edge Functions and server-side admin operations.
Session Management
Supabase automatically refreshes JWT tokens. The @trendingsociety/auth package handles this seamlessly.
Sessions persist across browser restarts using secure HTTP-only cookies.
Once logged in, users can navigate between products without re-authenticating.
Google OAuth Setup
Configure Redirect URIs
Add these URIs in Google Cloud Console: https://ymdccxqzmhxgbjbppywf.supabase.co/auth/v1/callback
http://localhost:3001/auth/callback (for development)
Add to Supabase
In Supabase Dashboard > Auth > Providers > Google:
Add Client ID
Add Client Secret
Test
pnpm dev --filter=dashboard
# Visit localhost:3001/login
Changes to OAuth configuration take 2-3 minutes to propagate. See Known Gotchas for common issues.
Environment Variables
# Required for all apps
NEXT_PUBLIC_SUPABASE_URL=https://ymdccxqzmhxgbjbppywf.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
# Server-side only
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
Never expose SUPABASE_SERVICE_ROLE_KEY to the client. It bypasses all RLS policies.