Skip to main content

Authentication System

Kinetic Email uses Supabase Auth with a multi-layer authentication and authorization model.

Authentication Methods

MethodImplementation
Magic Link (OTP)Email-based passwordless login
Google OAuthSocial login via Google
Email/PasswordTraditional credentials

All methods are configured through AuthContext.tsx and powered by the Supabase Auth SDK.

Auth Flow

User clicks "Sign In"


LoginModal → AuthContext.signIn()


Supabase Auth (magic link / OAuth / password)


JWT issued → stored in browser session

├── Frontend: AuthContext provides user state
│ └── Components use useAuth() hook

└── Backend: JWT passed as Bearer token
└── API functions call verifyUser(req, res)

Frontend Auth (AuthContext.tsx)

The AuthContext provides authentication state to the entire app:

const { user, isAdmin, signIn, signUp, signOut } = useAuth();

Key features:

  • Admin detection — Checks user_metadata.is_admin flag, verified against admin_users table
  • Profile management — User profiles stored in user_profiles table (display name, preferences)
  • Referral tracking — Captures ?ref= URL parameters and processes via process_referral() RPC
  • Guest → User sync — Learning progress from localStorage is synced to database on signup

Backend Auth (api/_auth.ts)

Two authentication levels for API endpoints:

verifyUser(req, res)

Validates the JWT from the Authorization: Bearer <token> header. Any logged-in user passes.

// Usage in any API endpoint
const user = await verifyUser(req, res);
if (!user) return; // 401 already sent

// user.id, user.email available

Returns:

  • user object on success
  • null on failure (sends 401 response automatically)

verifyAdmin(req, res)

Same as verifyUser plus checks the admin_users table for the user's email.

const user = await verifyAdmin(req, res);
if (!user) return; // 401 or 403 already sent

Returns:

  • user object on success
  • null on failure (sends 401 for invalid token, 403 for non-admin)

Token Spending (spendTokensServer)

Server-side token deduction for premium actions:

const tokenResult = await spendTokensServer(req, res, 'ai_generation');
if (!tokenResult) return; // 402 already sent

This creates a Supabase client authenticated as the user (using their JWT + anon key) so that auth.uid() resolves correctly inside the spend_tokens RPC function. The service role key is not used — this ensures RLS policies are respected.

Auth Modules

FileLanguageUsed By
api/_auth.tsTypeScriptsend-email.ts, send-email-ses.ts, generate-email.ts
api/admin/_auth.jsJavaScriptgenerate.js, all api/admin/*.js routes

Both modules export identical functions: verifyUser, verifyAdmin, spendTokensServer, setCorsHeaders.

Security Model

┌─────────────────────────────────────────────────┐
│ Layer 1: Supabase Auth (JWT validation) │
│ → verifyUser() — any authenticated user │
├─────────────────────────────────────────────────┤
│ Layer 2: Admin Check (admin_users table) │
│ → verifyAdmin() — email in admin_users table │
├─────────────────────────────────────────────────┤
│ Layer 3: Token Economy (spend_tokens RPC) │
│ → spendTokensServer() — sufficient balance │
├─────────────────────────────────────────────────┤
│ Layer 4: Supabase RLS (row-level security) │
│ → Database enforces auth.uid() = user_id │
└─────────────────────────────────────────────────┘