FormDee uses a secure Supabase-based authentication system with SHA-256 hashed API keys, rate limiting, and comprehensive audit trails.
- Purpose: Browser and UI access only
- Format: Any custom string (no restrictions)
- Used for:
- Login page (
/login) - Builder interface (
/builder)
- Login page (
- Storage: HTTP-only cookies after login
- Security: SHA-256 hashed in database
- Management: CLI-only via SSH
- Limit: Only one UI key allowed
- Example: Can be simple like
mypasswordor complex
- Purpose: Programmatic API access only
- Format: Random 43-character string (no prefix)
- Used for:
- External integrations
- CI/CD pipelines
- Automated scripts
- Third-party applications
- Storage: Never stored in browser
- Security: SHA-256 hashed in database
- Management: CLI-only via SSH
- Limit: Only one API key allowed
- Example:
LT-DBsreC1ftA9xLhe8O36olYwWulUuGscYPcd8MDko
The system detects the authentication source and validates accordingly:
| Source | Priority | Accepted Keys | Use Case |
|---|---|---|---|
Header (x-admin-key, x-api-key) |
1 | API Key ONLY | API clients, scripts |
Cookie (admin_key) |
2 | UI Key ONLY | Browser after login |
Query (?adminKey=) |
3 | API Key ONLY | Testing, simple integrations |
GET /api/forms?refKey={key}- Get single form configurationPOST /api/submit- Submit form dataGET /api/health- Health check
GET /api/forms- List all forms (UI/API key)POST /api/forms- Create/update form (UI/API key)DELETE /api/forms- Delete form (UI/API key)
GET /api/forms/sheets- Sheet metadata (UI/API key)POST /api/forms/test-slack- Test Slack webhook (UI/API key)GET /api/responses- Get form responses (UI/API key)POST /api/settings- Update settings (UI key only)POST /api/ai/generate- Generate form with AI (UI/API key)
// Login with UI key
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: 'your-ui-key' }),
})
// After login, cookie is set automatically
// Subsequent requests use the cookie
const forms = await fetch('/api/forms')# Using API key in header (recommended)
curl -H "x-api-key: your-api-key" \
https://your-domain.com/api/forms
# Using API key in query (backward compatibility)
curl "https://your-domain.com/api/forms?adminKey=your-api-key"// Using API key in header
const response = await fetch('https://your-domain.com/api/forms', {
headers: {
'x-api-key': process.env.ADMIN_API_KEY,
'Content-Type': 'application/json',
},
})-
SHA-256 Hashing
- All keys are hashed before storage
- Original keys cannot be recovered from database
- Keys are only shown once during generation
-
Rate Limiting
- Default: 60 requests per minute
- Default: 1000 requests per hour
- Configurable per key
-
Audit Trail
- Every API request is logged
- Tracks IP, endpoint, method, response time
- Usage statistics available in dashboard
-
Key Management
- Keys can have expiration dates
- Revocation with reason tracking
- Usage count and last used timestamp
- Permissions system (JSONB for flexibility)
-
Never share or expose keys
- Keep UI key private to authorized admin users
- Keep API key in secure environment variables
- Never commit keys to version control
- Keys are only shown once when generated
-
Use appropriate keys
- Browser access → UI key only
- Programmatic access → API key only
- Never use API key in frontend code
-
Rotate keys regularly
- Use the management UI at
/admin/api-keys - Or use CLI:
npx tsx scripts/generate-api-keys.ts - Update all integrations when rotating keys
- Use the management UI at
-
Monitor access
- Check audit logs in Supabase
- Review usage in
/admin/api-keys - Set up alerts for rate limit violations
| Error | Meaning | Solution |
|---|---|---|
| "You are not authorized to perform this action" | No key provided | Include valid key |
| "Invalid key type. This endpoint requires UI key" | API key used where UI key needed | Use UI key for browser |
| "Invalid key type. This endpoint requires API key" | UI key used where API key needed | Use API key for scripts |
# UI Key - for browser/admin access (generated via CLI)
ADMIN_UI_KEY=fmd_ui_YOUR_GENERATED_KEY_HERE
# API Key - for programmatic access (generated via CLI)
ADMIN_API_KEY=fmd_api_YOUR_GENERATED_KEY_HERE
# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
# Other settings
NODE_ENV=production
NEXT_PUBLIC_BASE_URL=https://your-domain.comAll key management must be done via SSH access to the server:
# Initial setup (interactive)
npx tsx scripts/setup-keys.ts
# Manage keys (set UI key or regenerate API key)
npx tsx scripts/manage-keys.ts
# Non-interactive reset for testing
npx tsx scripts/reset-keys.tsFeatures:
- Set custom UI key (any string)
- Generate random API key (auto-updates .env)
- View current keys (shows prefix only)
- Only one key per type allowed
- Automatic .env file updates
When migrating from plain text keys to Supabase Auth:
- Run the setup SQL in Supabase:
supabase-setup.sql - Generate new keys using:
npx tsx scripts/generate-initial-keys.ts - Update .env with the new generated keys
- Logout and login with the new UI key in browser
- Update API integrations to use the new API key
- Test thoroughly before deploying to production
- Check you're using the correct key for your access method
- Browser/cookie requests need UI key
- API/header requests need API key
- Verify ADMIN_UI_KEY is set correctly
- Check cookie settings in production
- Ensure HTTPS in production environment
- Confirm using ADMIN_API_KEY not UI key
- Pass key in header not cookie
- Check key hasn't been rotated