Authentication System - Feature Documentation
Owner: Priya Shah
Version: 0.4.1
| Owner | Last verified | Next verification due | Last CI run ID | Coverage % | Open risks |
|---|
| Priya Shah | 2025-12-28 | 2026-01-27 | ci.yml#2186 | Pending (auth API + Cypress + Playwright WebAuthn suites) | Redis-backed rate limits lack automation; OAuth provider sandboxes not load-tested |
Verification
- CI jobs:
ci.yml API + Cypress + Playwright stages (last run: ci.yml#2186)
- Commands (CI/staging demo portal only; never production tenants):
pnpm test:api --config jest.config.api.cjs --runTestsByPath tests/app/api/auth/signup/route.test.ts tests/app/api/auth/session/route.test.ts tests/app/api/auth/2fa/setup.test.ts tests/app/api/auth/2fa/verify.test.ts
pnpm cypress:run --spec "cypress/e2e/auth/**/*.cy.ts"
pnpm playwright:test tests/e2e/webauthn-passkey.spec.ts (WebAuthn E2E with virtual authenticators)
pnpm test -- --testPathPattern="webauthn" (WebAuthn unit tests)
pnpm db:health
- Test suites + environment links:
tests/app/api/auth/signup/route.test.ts (CI seeded demo DB, mocks Supabase Auth)
tests/app/api/auth/2fa/setup.test.ts, .../verify.test.ts, .../backup-codes.test.ts (CI seeded demo DB; exercises 2FA flows)
tests/lib/auth/webauthn.test.ts (WebAuthn service unit tests)
tests/e2e/webauthn-passkey.spec.ts (Playwright E2E: registration, login, management, recovery)
tests/auth/auth-suite.test.ts and tests/auth/signup-oauth.test.ts (unit/state coverage for auth UI + OAuth)
cypress/e2e/auth/login.cy.ts, google-login.cy.ts, oauth-signup-complete-flow.cy.ts, protected-pages.cy.ts (staging sandbox OAuth; demo portal only)
- Next verification due: 2026-01-27 (refresh CI run ID + coverage)
Table of Contents
- Problem / Job-to-Be-Done
- Solution Overview
- Scope
- Acceptance Criteria
- Dependencies & Risks
- Technical Architecture
- Security Considerations
- Testing Strategy
- Metrics & Success Criteria
- Rollout & Release Plan
- Observability & Monitoring
- Environment Variables
- API Reference
- Troubleshooting
- File Reference
- Completion Checklist
- Version History
Problem / Job-to-Be-Done
User Pain Points
| Pain Point | Impact | Solution |
|---|
| Insecure credential storage | Data breaches, compliance failures | Supabase Auth + encrypted tokens |
| Account takeover | Revenue loss, trust erosion | 2FA with TOTP + backup codes |
| Brute force attacks | Service degradation | Rate limiting (5 attempts/15 min) |
| Session hijacking | Unauthorized access | Secure cookies + CSRF protection |
| OAuth complexity | User friction during signup | Google OAuth with automatic account linking |
| Password fatigue | High support tickets | Password reset flow + OAuth alternatives |
Business Impact
| Metric | Risk if Unsolved | Target After Implementation |
|---|
| Security incidents | High liability exposure | Zero credential leaks |
| Signup abandonment | Lost revenue | <5% abandonment rate |
| Support tickets | High operational cost | <2% auth-related tickets |
| Compliance | Legal exposure | SOC2/GDPR ready |
| User trust | Churn risk | 99.9% auth availability |
Job Stories
When I'm signing up for Petunia,
I want a quick, secure process with multiple options (email/password or Google),
So that I can start using the platform without friction or security concerns.
When I suspect my account may be compromised,
I want to enable 2FA and generate backup codes,
So that my business data remains protected even if my password is leaked.
When I forget my password,
I want a secure reset process,
So that I can regain access without contacting support.
Solution Overview
User Journey
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTHENTICATION FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌─────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Landing │───▶│ Auth Page │───▶│ Verify │───▶│ Dashboard │ │
│ │ Page │ │ │ │ Email │ │ (Protected) │ │
│ └──────────┘ └─────────────┘ └────────────┘ └──────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Google │ │ 2FA │ │
│ │ OAuth │ │ Challenge │ │
│ └─────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Authentication Stack
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLIENT (Browser/App) │
│ useSession hook • Auth Context • Protected Routes • CSRF Token │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ API ROUTES (Next.js) │
│ /api/auth/signup • signin • signout • reset-password • 2fa/* │
│ Rate limiting • Input validation • Audit logging │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ AUTH LAYER (lib/auth) │
│ server.ts • session.ts • withAuth.ts • audit.ts • rate-limit.ts │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Supabase Auth │ │ Prisma (User) │
│ • Sessions │ │ • Profile data │
│ • OAuth │ │ • 2FA secrets │
│ • Password hash │ │ • Portal access │
└───────────────────┘ └───────────────────┘
Key Capabilities
| Capability | Implementation | Status |
|---|
| Email/Password Auth | Supabase signInWithPassword | ✅ Complete |
| Google OAuth | Supabase OAuth provider | ✅ Complete |
| Email Verification | Supabase + custom email service | ✅ Complete |
| Password Reset | Supabase updateUser | ✅ Complete |
| 2FA/MFA (TOTP) | Custom TOTP with backup codes | ✅ Complete |
| WebAuthn/Passkeys | @simplewebauthn + Prisma Passkey model | ✅ Complete |
| Session Management | Supabase + cookie sync | ✅ Complete |
| Rate Limiting | Custom with 5 attempts/15 min | ✅ Complete |
| CSRF Protection | Token rotation middleware | ✅ Complete |
| Audit Logging | 20+ event types tracked | ✅ Complete |
| Account Recovery | Partial signup recovery | ✅ Complete |
Scope
In Scope (v0.4.2)
Out of Scope (Future Versions)
Acceptance Criteria
Signup Flow
Feature: User Signup
As a new user
I want to create an account
So that I can access the Petunia platform
Scenario: Successful email/password signup
GIVEN I am on the signup page
AND I provide a valid email, password, and name
AND I accept the Terms of Service
WHEN I submit the signup form
THEN I should receive a verification email within 30 seconds
AND I should be redirected to the onboarding flow
AND my Prisma User record should be created
AND a TTFV milestone should be recorded
Scenario: Signup with existing email
GIVEN I provide an email that already exists
WHEN I submit the signup form
THEN I should see "User with this email already exists"
AND I should be offered a link to sign in instead
Scenario: Signup with weak password
GIVEN I provide a password shorter than 8 characters
WHEN I submit the signup form
THEN I should see a validation error
AND the form should not be submitted
Login Flow
Feature: User Login
As an existing user
I want to log in to my account
So that I can access my dashboard
Scenario: Successful email/password login
GIVEN I am a registered user
AND I provide correct credentials
WHEN I submit the login form
THEN I should be authenticated
AND session cookies should be set
AND I should be redirected to my default portal
Scenario: Login with 2FA enabled
GIVEN I have 2FA enabled on my account
AND I provide correct password
WHEN I submit the login form
THEN I should see the 2FA verification screen
AND my session should be marked as "2fa_pending"
Scenario: Login with incorrect credentials
GIVEN I provide incorrect password
WHEN I submit the login form
THEN I should see "Invalid email or password"
AND failed login attempt should be logged
AND failedLoginAttempts counter should increment
2FA Flow
Feature: Two-Factor Authentication
As a security-conscious user
I want to enable 2FA
So that my account is protected from unauthorized access
Scenario: Enable 2FA
GIVEN I am logged in
AND I navigate to security settings
WHEN I click "Enable 2FA"
THEN I should see a QR code for my authenticator app
AND I should receive a secret key
AND mfaSecret should be stored (encrypted)
Scenario: Verify 2FA setup
GIVEN I have scanned the QR code
AND I enter the 6-digit code from my authenticator
WHEN I submit the verification
THEN 2FA should be enabled on my account
AND I should receive 10 backup codes
AND mfaEnabled should be set to true
AND mfaBackupCodes should contain hashed codes
Scenario: Login with 2FA
GIVEN 2FA is enabled on my account
AND I have entered my password
WHEN I enter a valid TOTP code
THEN I should be fully authenticated
AND the 2fa_pending cookie should be cleared
Password Reset Flow
Feature: Password Reset
As a user who forgot my password
I want to reset it securely
So that I can regain access to my account
Scenario: Request password reset
GIVEN I am on the forgot password page
AND I provide my registered email
WHEN I submit the form
THEN I should see "Check your email for reset instructions"
AND a password reset email should be sent
AND PASSWORD_RESET_REQUESTED audit event should be logged
Scenario: Complete password reset
GIVEN I have received a reset email
AND I click the reset link
AND I provide a new password (8+ characters)
WHEN I submit the new password
THEN my password should be updated
AND I should be able to log in with the new password
AND PASSWORD_RESET_COMPLETED audit event should be logged
Dependencies & Risks
External Dependencies
| Dependency | Purpose | Fallback | Risk Level |
|---|
| Supabase Auth | Session management, password hashing | None (core dependency) | 🔴 Critical |
| Supabase Database | User metadata, OAuth tokens | None (core dependency) | 🔴 Critical |
| Prisma | User profile, portal access | None (core dependency) | 🔴 Critical |
| SendGrid | Email verification, password reset | Console logging (dev) | 🟡 Medium |
| Google OAuth | Social login | Email/password fallback | 🟢 Low |
Internal Dependencies
| Dependency | Purpose | Owner |
|---|
/lib/database | Prisma client | Core Team |
/lib/utils/supabase/* | Supabase clients | Core Team |
/lib/services/email-verification | Verification emails | Email Team |
/lib/utils/cookie-config | Cookie management | Core Team |
Risks & Mitigations
| Risk | Probability | Impact | Mitigation |
|---|
| Supabase outage | Low | Critical | Health check endpoints, graceful error handling |
| Email delivery failure | Medium | High | Retry logic, SendGrid webhook monitoring |
| Rate limit bypass | Low | High | Multiple limit layers (edge, API, action) |
| Token expiry race | Medium | Medium | Refresh before expiry, session recovery |
| 2FA lockout | Medium | Medium | Backup codes, support recovery process |
| CSRF token mismatch | Medium | Low | Token refresh on page load, clear error messages |
Technical Architecture
Authentication Flow Diagram
SIGNUP FLOW
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. Client POST /api/auth/signup │
│ ├── Rate limit check (5/15min) │
│ ├── Input validation (Zod schema) │
│ └── TOS acceptance required │
│ │
│ 2. Supabase signUp(email, password) │
│ ├── Password hashed (bcrypt) │
│ ├── Session created │
│ └── Auth cookies set │
│ │
│ 3. Prisma User creation │
│ ├── Generate unique user ID │
│ ├── Create User record │
│ ├── Create default Portal │
│ └── Create UserPortalAccess │
│ │
│ 4. Verification email sent │
│ ├── Generate token │
│ └── SendGrid delivery │
│ │
│ 5. Redirect to /onboarding?pid={portalId}&new=true │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
LOGIN FLOW
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ 1. Client POST /api/auth/signin │
│ ├── Rate limit check │
│ └── Input validation │
│ │
│ 2. Supabase signInWithPassword(email, password) │
│ ├── Credentials verified │
│ └── Session created │
│ │
│ 3. Prisma User lookup │
│ ├── Find by email │
│ ├── Include UserPortalAccess │
│ └── Check mfaEnabled │
│ │
│ 4. If 2FA enabled: │
│ ├── Return requiresTwoFactor: true │
│ ├── Set 2fa_pending cookie (5 min) │
│ └── Wait for /api/auth/2fa/verify │
│ │
│ 5. Return user data + portal │
│ └── Set session cookies │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Key Interfaces
// lib/auth/types.ts
interface AuthConfig {
session: {
strategy: 'jwt';
maxAge: number; // 30 days
updateAge: number; // 24 hours
};
cookie: {
httpOnly: boolean;
secure: boolean;
sameSite: 'lax' | 'strict';
maxAge: number;
};
routes: {
signIn: '/auth/signin';
signUp: '/auth/signup';
signOut: '/auth/signout';
error: '/auth/error';
verifyRequest: '/auth/verify-request';
newUser: '/onboarding';
};
}
// lib/types/auth.ts
interface AuthUser {
id: string;
name: string | null;
email: string;
image?: string | null;
role?: 'admin' | 'manager' | 'agent' | 'viewer' | 'user';
isPlatformAdmin?: boolean;
stripeCustomerId?: string | null;
subscriptionStatus?: 'active' | 'canceled' | 'past_due' | 'trialing';
onboardingCompleted?: boolean;
portalAccess?: Array<{ portalId: string; role?: string }>;
defaultPortalId?: string | null;
}
interface AuthSession {
user: AuthUser;
expires: string;
accessToken?: string;
onboardingMode?: 'voice' | 'voice-browser' | 'text' | 'quick-setup' | null;
onboardingCompleted?: boolean;
}
Rate Limiting Configuration
// lib/auth/rate-limit.ts
export const AUTH_RATE_LIMITS = {
LOGIN: {
maxAttempts: 5,
windowMs: 15 * 60 * 1000, // 15 minutes
blockDurationMs: 30 * 60 * 1000, // 30 minutes
},
SIGNUP: {
maxAttempts: 3,
windowMs: 60 * 60 * 1000, // 1 hour
blockDurationMs: 24 * 60 * 60 * 1000, // 24 hours
},
PASSWORD_RESET: {
maxAttempts: 3,
windowMs: 60 * 60 * 1000, // 1 hour
blockDurationMs: 60 * 60 * 1000, // 1 hour
},
OTP_VERIFICATION: {
maxAttempts: 5,
windowMs: 5 * 60 * 1000, // 5 minutes
blockDurationMs: 15 * 60 * 1000, // 15 minutes
},
};
Security Considerations
Password Security
| Aspect | Implementation |
|---|
| Hashing | bcrypt via Supabase (cost factor 10) |
| Minimum Length | 8 characters enforced |
| Strength Validation | Zod schema + custom password-strength.ts |
| Storage | Passwords never stored in Prisma - Supabase only |
2FA Security
| Aspect | Implementation |
|---|
| Algorithm | TOTP (RFC 6238) |
| Secret Storage | Encrypted in User.mfaSecret |
| Backup Codes | 10 codes, SHA-256 hashed |
| Time Window | ±1 step (30 seconds each) |
| Rate Limiting | 5 attempts per 5 minutes |
Session Security
| Aspect | Implementation |
|---|
| Cookie Flags | httpOnly, secure (prod), sameSite: 'lax' |
| Session Duration | 30 days max, 24-hour refresh |
| CSRF Protection | Token rotation, double-submit cookie |
| Session Revocation | Supabase signOut clears all tokens |
Audit Events
enum AuditEventType {
// Authentication
LOGIN_SUCCESS = 'LOGIN_SUCCESS',
LOGIN_FAILED = 'LOGIN_FAILED',
LOGOUT = 'LOGOUT',
PASSWORD_RESET_REQUESTED = 'PASSWORD_RESET_REQUESTED',
PASSWORD_RESET_COMPLETED = 'PASSWORD_RESET_COMPLETED',
PASSWORD_CHANGED = 'PASSWORD_CHANGED',
EMAIL_VERIFIED = 'EMAIL_VERIFIED',
// 2FA
TWO_FA_ENABLED = 'TWO_FA_ENABLED',
TWO_FA_DISABLED = 'TWO_FA_DISABLED',
TWO_FA_VERIFIED = 'TWO_FA_VERIFIED',
TWO_FA_FAILED = 'TWO_FA_FAILED',
// Account
ACCOUNT_CREATED = 'ACCOUNT_CREATED',
ACCOUNT_LOCKED = 'ACCOUNT_LOCKED',
ACCOUNT_UNLOCKED = 'ACCOUNT_UNLOCKED',
// Security
SUSPICIOUS_ACTIVITY = 'SUSPICIOUS_ACTIVITY',
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
INVALID_CSRF_TOKEN = 'INVALID_CSRF_TOKEN',
}
Testing Strategy
Unit Tests
| Test File | Tests | Coverage |
|---|
/tests/lib/utils/auth.test.ts | Core auth utilities | Functions |
/tests/lib/utils/authSchemas.test.ts | Zod validation schemas | Input validation |
/tests/lib/utils/auth-errors.test.ts | Error handling | Error codes |
/tests/lib/utils/auth-routes.test.ts | Route protection | Middleware |
/tests/lib/utils/auth-types.test.ts | Type definitions | Type guards |
/tests/lib/utils/auth-audit-logger.test.ts | Audit logging | Event logging |
/tests/lib/utils/websocket-auth.test.ts | WebSocket auth | Real-time auth |
/tests/unit/secure-auth-state.test.ts | State management | Auth state |
Integration Tests
| Test File | Tests | Coverage |
|---|
/tests/api/auth/auth-flow-integration.test.ts | Full auth flows | E2E flows |
/tests/auth/auth-suite.test.ts | Auth suite | All features |
/tests/auth/signup-oauth.test.ts | OAuth flows | Google OAuth |
/tests/integration/auth-tenancy.test.ts | Multi-tenant auth | Portal isolation |
/tests/integration/flows/03-oauth-connect-sync.test.ts | OAuth connection | Sync flow |
E2E Tests (Cypress)
# Run auth E2E tests
pnpm cypress run --spec "cypress/e2e/auth/**/*.cy.ts"
Manual Testing Checklist
Metrics & Success Criteria
| KPI | Target | Current | Status |
|---|
| Auth response time | <500ms | ~200ms | ✅ |
| Signup success rate | >95% | ~97% | ✅ |
| Login success rate | >98% | ~99% | ✅ |
| 2FA adoption rate | >10% | ~5% | 🟡 |
| Password reset completion | >80% | ~85% | ✅ |
| Rate limit triggers | <1% of requests | ~0.2% | ✅ |
Service Level Objectives
| SLO | Target | Measurement |
|---|
| Availability | 99.9% | Auth endpoints uptime |
| Latency (p50) | <200ms | Median response time |
| Latency (p99) | <1000ms | 99th percentile |
| Error rate | <0.1% | 5xx responses |
Rollout & Release Plan
Feature Flags
| Flag | Purpose | Default |
|---|
AUTH_SIGNUP | Enable/disable signup | true |
AUTH_2FA | Enable 2FA feature | true |
AUTH_GOOGLE_OAUTH | Enable Google login | true |
Rollout Phases
| Phase | Audience | Duration | Success Criteria |
|---|
| 1. Internal | Team only | 1 week | All tests pass |
| 2. Beta | 100 users | 2 weeks | <1% error rate |
| 3. GA | All users | Ongoing | KPIs met |
Observability & Monitoring
Logging
// All auth operations use structured logging
logger.info('Signup attempt started', {
requestId,
clientIp,
userAgent: request.headers.get('user-agent'),
});
logger.error('Signin error', {
error: errorMessage,
requestId,
});
Key Log Patterns
| Pattern | Meaning | Action |
|---|
LOGIN_FAILED | Invalid credentials | Monitor for brute force |
RATE_LIMIT_EXCEEDED | Too many attempts | Check for attacks |
TWO_FA_FAILED | Invalid TOTP code | Monitor for token theft |
ACCOUNT_LOCKED | Lockout triggered | May need support |
Alerts
| Alert | Condition | Severity |
|---|
| Auth Error Spike | >5% error rate in 5 min | 🔴 Critical |
| Rate Limit Surge | >100 blocks in 1 min | 🟡 Warning |
| 2FA Failure Spike | >10 failures per user | 🟡 Warning |
| Supabase Latency | >2s p99 | 🔴 Critical |
Environment Variables
Required
| Variable | Description | Example |
|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL | https://xxx.supabase.co |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Supabase anon key | eyJ... |
SUPABASE_SERVICE_ROLE_KEY | Supabase service key | eyJ... |
DATABASE_URL | PostgreSQL connection | postgresql://... |
Optional
| Variable | Description | Default |
|---|
AUTH_SECRET | Session encryption | Random if not set |
NEXTAUTH_URL | Base URL | Auto-detected |
GOOGLE_CLIENT_ID | OAuth client ID | None |
GOOGLE_CLIENT_SECRET | OAuth client secret | None |
API Reference
POST /api/auth/signup
// Request
{
name: string;
email: string;
password: string;
tosAccepted: boolean;
voiceConsent?: boolean;
}
// Response (201)
{
success: true;
data: {
user: { id, email, name, role };
hasSession: boolean;
portal: { id, name, petuniaID };
nextStep: '/onboarding';
};
}
POST /api/auth/signin
// Request
{
email: string;
password: string;
}
// Response (200)
{
success: true;
data: {
user: { id, email, name, role, onboardingCompleted };
portal: { id, petuniaID, name };
requiresTwoFactor: boolean;
};
}
POST /api/auth/2fa/setup
// Response (200)
{
success: true;
secret: string; // Base32 TOTP secret for manual entry
otpauthUrl: string; // otpauth:// URL for QR code generation
qrCode: string; // Base64 data URL of QR code (data:image/png;base64,...)
message: string; // User instructions
}
POST /api/auth/2fa/verify
// Request
{
token: string; // 6-digit TOTP
}
// Response (200)
{
success: true;
backupCodes: string[]; // 10 codes (shown once)
warning: string;
}
POST /api/auth/reset-password
// Request
{
token: string;
password: string;
type?: 'invite' | 'reset';
}
// Response (200)
{
message: 'Password has been reset successfully';
}
POST /api/auth/passkey/register/options
// Request: No body required (uses authenticated session)
// Response (200)
{
success: true;
options: {
challenge: string; // Base64URL encoded challenge
rp: { name: string; id: string };
user: { id: string; name: string; displayName: string };
pubKeyCredParams: Array<{ type: 'public-key'; alg: number }>;
timeout: number;
attestation: 'none';
excludeCredentials: Array<{ id: string; type: 'public-key'; transports: string[] }>;
authenticatorSelection: {
residentKey: 'preferred';
userVerification: 'preferred';
authenticatorAttachment: 'platform';
};
};
}
POST /api/auth/passkey/register/verify
// Request
{
response: {
id: string;
rawId: string;
response: {
attestationObject: string;
clientDataJSON: string;
transports?: string[];
};
clientExtensionResults: object;
type: 'public-key';
};
name?: string; // Optional friendly name for the passkey
}
// Response (200)
{
success: true;
verified: true;
message: 'Passkey registered successfully';
}
POST /api/auth/passkey/login/options
// Request
{
email?: string; // Optional - for usernameless flow, omit email
}
// Response (200)
{
success: true;
options: {
challenge: string;
rpId: string;
timeout: number;
userVerification: 'preferred';
allowCredentials?: Array<{ id: string; type: 'public-key'; transports: string[] }>;
};
}
POST /api/auth/passkey/login/verify
// Request
{
response: {
id: string;
rawId: string;
response: {
authenticatorData: string;
clientDataJSON: string;
signature: string;
userHandle?: string;
};
clientExtensionResults: object;
type: 'public-key';
};
}
// Response (200)
{
success: true;
user: { id, email, name, role, onboardingCompleted };
portal: { id, petuniaID, name } | null;
requiresTwoFactor: false; // Passkeys provide strong auth
}
GET /api/auth/passkey
// Response (200) - List all passkeys for authenticated user
{
success: true;
passkeys: Array<{
id: string;
name: string;
deviceType: 'singleDevice' | 'multiDevice';
backedUp: boolean;
createdAt: string;
lastUsedAt: string | null;
}>;
}
DELETE /api/auth/passkey
// Request
{
id: string; // UUID of passkey to delete
}
// Response (200)
{
success: true;
message: 'Passkey deleted successfully';
}
PATCH /api/auth/passkey
// Request
{
id: string; // UUID of passkey
name: string; // New name (1-100 chars)
}
// Response (200)
{
success: true;
message: 'Passkey renamed successfully';
}
Troubleshooting
Common Issues
"Invalid login credentials"
- Verify email/password combination
- Check if email is verified
- Check if account is locked (wait 30 min or contact support)
"User with this email already exists"
- Use the sign-in page instead
- Check for OAuth account with same email
"2FA verification failed"
- Ensure authenticator time is synced
- Try a backup code
- Wait for next 30-second window
"Rate limit exceeded"
- Wait 15-30 minutes before retrying
- Contact support if legitimate use case
"CSRF verification failed"
- Refresh the page
- Clear cookies and retry
- Ensure JavaScript is enabled
Debug Commands
# Check user auth state
pnpm tsx scripts/debug-user.ts --email user@example.com
# Verify Supabase connection
pnpm tsx scripts/check-supabase.ts
# Test rate limiter
pnpm tsx scripts/test-rate-limit.ts --endpoint signin
File Reference
Core Auth Files
| File | Purpose | Lines |
|---|
/lib/auth/index.ts | Main exports | ~30 |
/lib/auth/server.ts | Server-side auth | ~400 |
/lib/auth/session.ts | Session management | ~150 |
/lib/auth/config.ts | Auth configuration | ~80 |
/lib/auth/types.ts | Internal types | ~100 |
/lib/auth/client.ts | Client-side hooks | ~200 |
/lib/auth/audit.ts | Audit logging | ~215 |
/lib/auth/rate-limit.ts | Rate limiting | ~100 |
/lib/auth/otp-rate-limit.ts | OTP rate limiting | ~80 |
/lib/auth/password-strength.ts | Password validation | ~50 |
/lib/auth/withAuth.ts | Auth middleware | ~80 |
/lib/auth/auth-helpers.ts | Helper re-exports | ~15 |
/lib/auth/auth-monitor.ts | Signup monitoring | ~60 |
API Routes
| Route | Methods | Purpose |
|---|
/api/auth/signup | POST | User registration |
/api/auth/signin | POST, GET | User login |
/api/auth/signout | POST | User logout |
/api/auth/reset-password | POST | Password reset |
/api/auth/forgot-password | POST | Request reset email |
/api/auth/2fa/setup | POST, GET | 2FA setup |
/api/auth/2fa/verify | POST | Verify TOTP |
/api/auth/2fa/disable | POST | Disable 2FA |
/api/auth/2fa/backup-codes | POST | Regenerate backup codes |
/api/auth/passkey | GET, PATCH, DELETE | List, rename, delete passkeys |
/api/auth/passkey/register/options | POST | Get passkey registration options |
/api/auth/passkey/register/verify | POST | Verify passkey registration |
/api/auth/passkey/login/options | POST | Get passkey login options |
/api/auth/passkey/login/verify | POST | Verify passkey login |
Client Hooks
| Hook | Location | Purpose |
|---|
useSession | /lib/auth/client.ts | Get current session |
getServerSession | /lib/hooks/auth/getServerSession.ts | Server-side session |
Completion Checklist
Implementation
Testing
Documentation
Operations
Version History
| Version | Date | Changes |
|---|
| 0.4.2 | 2025-12-28 | WebAuthn/Passkey support with @simplewebauthn, E2E tests with Playwright virtual authenticators |
| 0.4.1 | 2025-12-05 | Added verification snapshot, owner map reference, and explicit test/CI links |
| 0.4.0 | 2025-12-03 | QR code generation, 2FA disable flow with TOTP/backup codes |
| 0.3.9 | 2025-12-03 | Complete documentation, 99% feature coverage |
| 0.3.0 | 2025-11-30 | Added tenant isolation for non-platform admins |
| 0.2.0 | 2025-09-15 | Added 2FA with TOTP and backup codes |
| 0.1.0 | 2025-07-10 | Initial Supabase integration |
Completed:
- QR code generation for 2FA setup
- Full 2FA settings UI (enable/disable with TOTP or backup codes)
- API contracts match implementation
- WebAuthn/Passkey support with full API (registration, authentication, management)
- E2E tests for WebAuthn using Playwright virtual authenticators
- Unit tests for WebAuthn service
Generated: December 28, 2025