Authentication
Overview
GigPilot AI uses Supabase Auth for identity management with a custom JWT-based session system for API access.
Auth Methods
1. Google OAuth
Primary authentication method via Supabase Auth.
Flow:
- Frontend initiates Google OAuth via Supabase
- User authenticates with Google
- Supabase redirects to
/auth/callback - Callback page exchanges code for session
- Session token sent to backend
/api/auth/loginfor profile sync - Backend returns a session JWT for API access
Frontend Initiation:
const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${window.location.origin}/auth/callback` }});2. Magic Link
Passwordless email authentication.
Endpoint: POST /api/auth/magic-link
Request:
{ "email": "user@example.com"}Response:
{ "success": true, "message": "Magic link generated successfully", "data": { "magicUrl": "https://gigpilot.ai/auth/magic-verify?token=..." }}The magic link token expires after 15 minutes.
3. Email/Password Login
Used for backend profile sync after OAuth.
Endpoint: POST /api/auth/login
Request:
{ "email": "user@example.com", "fullName": "John Doe", "password": "optional-password"}Response:
{ "success": true, "message": "Logged in successfully", "data": { "token": "base64url-encoded-jwt", "user": { "id": "uuid", "email": "user@example.com", "full_name": "John Doe", "avatar_url": "https://...", "role": "Pro", "credits_remaining": 450, "monthly_quota": 500 } }}JWT Token Format
Tokens are base64url-encoded JSON containing:
{ "userId": "uuid", "email": "user@example.com", "role": "Pro", "exp": 1722470400000}Token expiry: 7 days from creation.
Note: Tokens are NOT standard JWTs with HMAC signatures. They use Supabase Auth for verification first, then fall back to local base64 decoding.
Using Tokens
Include the token in the Authorization header:
Authorization: Bearer <token>Token Verification
The backend verification flow:
- Extract token from
Authorizationheader - Attempt Supabase
getUser()verification - If Supabase fails, fall back to base64 local decode
- Check expiry timestamp
- Return payload or
null
Protected Routes
Routes requiring authentication use the authenticate middleware:
fastify.get('/api/protected-route', { preHandler: [authenticate]}, handler);The middleware attaches req.user with:
{ userId: string; email: string; role: UserRole; // 'Free' | 'Pro' | 'Agency' | 'Admin'}Role Permissions
| Role | Hierarchy | Permissions |
|---|---|---|
| Free | 0 | Basic features, 50 credits |
| Pro | 1 | Full features, 500 credits |
| Agency | 2 | All features, 2000 credits |
| Admin | 3 | Full system access |
Higher roles include all permissions of lower roles.
Session Management (Frontend)
The frontend API client handles session lifecycle:
- Token Retrieval: Checks Supabase session, falls back to localStorage
- Auto-Refresh: Attempts Supabase session refresh on 401
- Retry Logic: Retries failed requests once with refreshed token
- Expiry Handling: Clears stored tokens and redirects to
/login
// Frontend API client usageimport { api } from '@/lib/api-client';
const data = await api.get('/api/history');Logout Flow
Frontend clears local storage and Supabase session:
localStorage.removeItem('gp_token');localStorage.removeItem('gp_user');await supabase.auth.signOut();window.location.href = '/login';Security Notes
- Tokens are verified against Supabase Auth when available
- Local base64 fallback is used for development/testing
- Sensitive fields are redacted in logs via Pino configuration
- CORS restricts origins to configured
FRONTEND_URLvalues - Rate limiting prevents brute-force attacks (100 req/min)
- HttpOnly cookies signed with JWT_SECRET for cookie-based flows

