Authentication Architecture
The Starter Kit uses server actions defined inapp/actions.ts to handle all authentication operations:
- Server-Side Processing: All auth logic runs on the server to protect sensitive credentials
- JWT Token Storage: Access tokens (30 min) and refresh tokens (7 days) stored in secure httpOnly cookies
- Role-Based Registration: End users register via deployed project-mode applications
- Universal Login: Single login flow for all user roles with automatic role-based redirects
- Project-Scoped Authentication: End user requests include X-Project-ID header for project context
- FastAPI endpoint: POST /api/v1/auth/register (role determined from headers)
- Command: RegisterUserCommand with email, password, role, project_id, full_name
- Handler: RegisterUserHandler delegates to role-specific registration methods
- Aggregate: UserActions.register() validates credentials and emits UserWasRegistered event
- Projector: UserReadModelProjector rebuilds User table from events
Registration Flows
Unified Registration Endpoint
The backend uses a single/api/v1/auth/register endpoint that determines user role from request headers:
- X-Operator-Key header present → Creates DEVELOPER role
- X-Developer-Key + X-Project-ID headers present → Creates END_USER role
- No valid auth headers → Returns 400 error
The
full_name field allows users to provide a display name for personalized UI elements like dashboard greetings.End-User Registration
End users register through your deployed project-mode application with project-scoped access: Frontend Flow:- User navigates to
/registerpage - Submits email, password, and optional full_name
backendRegisterAction()validates input and constructs request- Server action POSTs to
/api/v1/auth/registerwith headers - Backend creates user and returns JWT tokens
- Tokens stored in httpOnly cookies
- User redirected to
/dashboard
- Endpoint Receives Request: POST /api/v1/auth/register with X-Developer-Key and X-Project-ID headers
- Role Resolution:
resolve_role_from_headers()returns UserRole.END_USER based on X-Developer-Key presence - Project ID Validation: Parses X-Project-ID as UUID, validates format
- Developer Authentication: Verifies X-Developer-Key SHA-256 hash against DeveloperKey table
- Project Ownership Check: Queries Project table to verify developer owns project_id
- Email Availability Check:
validate_email_availability()ensures email not taken within project scope - Command Creation: Builds RegisterUserCommand with email, password, UserRole.END_USER, project_id, full_name
- Aggregate Registration: UserActions.register() validates password (min 8 chars, uppercase, lowercase, digit), hashes with bcrypt, emits UserWasRegistered event
- Event Persistence: EventSourcedRepository saves event to event_store table
- JWT Generation: Creates access_token (30 min expiry) and refresh_token (7 days) with HS256 algorithm
- Project Assignment: Handler calls
_assign_user_to_project()to create ProjectUser record - Email Verification: Emits EmailVerificationWasRequested event with 24h token
- Response: Returns RegistrationResponse with user data, project_id, access_token, refresh_token
full_name: Optional display name provided during registrationproject_id: UUID of the project the end user belongs to (required for END_USER)is_active: Always false initially, requires email verificationaccess_token: JWT with claims: sub (user_id), type (“access”), exp (30 min), project_idrefresh_token: JWT with claims: sub (user_id), type (“refresh”), exp (7 days)
- END_USER emails must be unique within a project (enforced by unique constraint)
- OPERATOR/DEVELOPER emails must be globally unique (enforced by partial index on NULL project_id)
- Same email can exist as END_USER in multiple projects
- END_USER email can coexist with OPERATOR/DEVELOPER email
Developer Registration
Developer registration is handled through the Cloud Admin console at devkit4ai.com or vibecoding.ad. The Starter Kit includes developer registration support for compatibility but redirects project mode users to Cloud Admin. In project mode, the developer registration page redirects to
/login.- Developer navigates to
/register/developer - Submits email and password
backendRegisterAction()sends request withrole: "developer"- Cloud API validates X-Operator-Key header
- Backend creates developer with auto-provisioning
- Returns provisioning bundle (project_id, developer_key, api_key)
- Stores provisioning in httpOnly cookie (24h TTL)
- Redirects to
/register/developer/success?email=<email>
- Default Project: Created with name “Default Project”
- API Key: Generated for the project (prefix
ak_+ 32 URL-safe chars via secrets.token_urlsafe) - Developer Key: Generated and linked to project (prefix
ak_+ 32 URL-safe chars, SHA-256 hashed)
- Format:
ak_+ secrets.token_urlsafe(32) → 46 character string - Storage: SHA-256 hash in database, full key shown once
- Key prefix changed from
dk_toak_in v1.5.0
Login Flow
Universal Login
The login page handles all user types with role-based redirects after authentication: Frontend Flow:- Endpoint Receives Request: POST /api/v1/auth/login with optional X-Project-ID header
- Command Creation: LoginUserCommand with email, password, project_id (if provided)
- User Lookup: Queries User table by email and project_id (for END_USER) or email only (for OPERATOR/DEVELOPER)
- Password Verification: Uses bcrypt via
pwd_context.verify(password, user.hashed_password) - Active Status Check: Validates
user.is_activeis True (email verified) - Aggregate Loading: Reconstructs UserActions from event stream via
from_events() - Login Method: UserActions.login() emits UserWasLoggedIn event
- Event Persistence: EventSourcedRepository saves event to event_store
- JWT Generation: Creates access_token and refresh_token with HS256 algorithm
- Response: Returns TokenResponse with access_token, refresh_token, token_type
- Email and password validation
- Return URL preservation with security validation
- Error message display from query params
- Link to registration page
- 10 second timeout protection via AbortController
Project-Scoped Authentication
End User Login Requirements: End users must provide project context for authentication:- Enables same email to exist as END_USER in multiple projects
- Isolates user namespaces per project
- Developer A’s end users cannot access Developer B’s project
- JWT access tokens for END_USER include
project_idclaim
The
X-Project-ID header is crucial for end user authentication. It ensures all requests are scoped to the correct project context. Without it, end user login will fail.Return URL Handling
The login flow preserves the user’s intended destination with security validation:- Only same-origin relative paths allowed
- Must start with single forward slash
/ - Rejects
//prefix (prevents open redirects to external sites) - Rejects backslashes and control characters
- Maximum 2048 characters
- URL-decoded before validation
JWT Token Management
Token Storage
Tokens stored in secure httpOnly cookies with protocol-based security:- Not accessible via JavaScript (prevents XSS attacks)
- Automatically sent with requests to same origin
- Protected from client-side tampering
- Server-side only access via
cookies()from next/headers
Token Lifecycle
1. Registration/Login:- Backend generates both tokens with HS256 algorithm
- Frontend stores in httpOnly cookies via
storeTokensInCookies() - Cookies sent automatically with subsequent requests
- Access token expires after 30 minutes (JWT
expclaim) - Backend returns 401 Unauthorized for expired tokens
- Frontend must use refresh token to obtain new access token
Accessing Current User
Server Components:- Caches result per-request to avoid redundant API calls
- Multiple calls to
getCurrentUser()in same request cycle return same data - Cache automatically invalidated between requests
- Fresh user data fetched on each new page load
The
project_id field is only present for end users and identifies which project they belong to. Developers and operators do not have a project_id.Personalized Greetings
Usefull_name for personalized UI elements:
- With
full_name: “Welcome, Sarah Johnson!” - Without
full_name: “Welcome!”
x-invoke-pathheader (Next.js edge runtime)x-pathnameheader (custom header from middleware)x-urlheader (parse pathname and search)refererheader (parse pathname and search)- Default:
/
Role-Based Protection
UserequireRole() to enforce role-based access control:
Client-Side Protection
Use hooks for conditional rendering without redirects:- Algorithm: bcrypt via passlib.context.CryptContext
- Work factor: Default bcrypt rounds (2^12 iterations)
- Stored in
users.hashed_passwordcolumn (VARCHAR 255) - Never logged or returned in API responses
Role-Based Headers
All API requests include role-specific headers for authentication and authorization: End User Requests (Project Mode):Developer keys and API keys changed from
dk_ prefix to ak_ prefix in v1.5.0. The format is ak_ + 32 URL-safe characters generated by secrets.token_urlsafe(32).Error Handling
Registration Errors
Common registration error scenarios with backend triggering conditions:| Error | HTTP Status | Cause | Backend Condition | Resolution |
|---|---|---|---|---|
| ”Email already registered” | 409 Conflict | Duplicate account | validate_email_availability() finds existing user with same email+project_id | Use existing account or contact support |
| ”End user registration requires a developer key and project context” | 400 Bad Request | Missing developer key | X-Developer-Key header not present for END_USER | Configure DEVKIT4AI_DEVELOPER_KEY |
| ”X-Project-ID header is required for END_USER registration” | 400 Bad Request | Missing project ID | X-Project-ID header not present when X-Developer-Key provided | Configure DEVKIT4AI_PROJECT_ID |
| ”Invalid X-Project-ID format. Must be a valid UUID.” | 400 Bad Request | Malformed project ID | uuid.UUID(project_id_header) raises ValueError | Fix project ID format in env vars |
| ”Project not found or you don’t have permission to add users to it” | 403 Forbidden | Invalid project ownership | Project query with developer_id + project_id returns None | Verify project belongs to developer |
| ”Password must be at least 8 characters long” | 500 Internal | Weak password | len(password) < 8 in UserActions.register() | Use stronger password |
| ”Password must contain at least one uppercase letter” | 500 Internal | Missing uppercase | not any(c.isupper() for c in password) | Add uppercase letter |
| ”Password must contain at least one lowercase letter” | 500 Internal | Missing lowercase | not any(c.islower() for c in password) | Add lowercase letter |
| ”Password must contain at least one digit” | 500 Internal | Missing digit | not any(c.isdigit() for c in password) | Add digit |
| ”Invalid email format” | 500 Internal | Bad email | Email missing ’@’ or len < 5 in aggregate | Fix email format |
| ”Network error” | N/A | API unreachable | Fetch throws network exception | Check NEXT_PUBLIC_API_URL |
| ”Registration timed out” | N/A | Request timeout | AbortController timeout after 10 seconds | Check backend availability |
| ”Too many registration attempts” | 429 Too Many Requests | Rate limiting | Backend rate limiter triggered | Wait and retry |
| ”Operator key is not configured” | N/A | Missing operator key | DEVKIT4AI_OPERATOR_KEY not set for developer registration | Configure operator key |
| ”Provisioning data is missing” | N/A | Incomplete provisioning | Developer registration response missing project_id/developer_key/api_key | Contact platform support |
Login Errors
Common login error scenarios with backend triggering conditions:| Error | HTTP Status | Cause | Backend Condition | Resolution |
|---|---|---|---|---|
| ”Invalid credentials” | 401 Unauthorized | Wrong email/password | pwd_context.verify() returns False | Check credentials or reset password |
| ”Account not activated. Please check your email for the verification link.” | 401 Unauthorized | Email not verified | user.is_active == False | Check email for verification |
| ”Session expired” | 401 Unauthorized | JWT token expired | JWT exp claim < current time | Log in again |
| ”Request timeout” | N/A | Request timeout | AbortController timeout after 10 seconds | Check backend availability |
| ”Email and password are required” | N/A | Missing credentials | email or password is empty | Provide both fields |
| ”Application is not properly configured” | N/A | Missing backend URL | NEXT_PUBLIC_API_URL not set | Configure backend URL |
| User not found | 401 Unauthorized | Email doesn’t exist | User query by email+project_id returns None | Check email or register |
| Project context missing | 401 Unauthorized | Missing X-Project-ID for END_USER | END_USER login without X-Project-ID header | Configure project ID in project mode |
Frontend Error Handling
Provisioning Bundle
Provisioning bundles are only used for developer registration in console/operator modes. End users do not receive provisioning data. In project mode (Starter Kit), developer registration is disabled and redirects to Cloud Admin.
- Name:
devkit4ai-provisioning - Expiry: 24 hours (86400 seconds)
- Flags: httpOnly (not accessible to JavaScript), secure (HTTPS only), sameSite=lax
- Path:
/(accessible to all routes)
/register/developer/success page shows these credentials once using consumeProvisioningBundle():
Logout Flow
Sign out action clears all auth state and provisioning data:- Server action
signOutAction()called from form clearTokensFromCookies()deletes all auth cookies- User redirected to
/loginpage - No backend API call required (stateless JWT tokens)
devkit4ai-token: JWT access tokendevkit4ai-refresh-token: JWT refresh tokendevkit4ai-provisioning: Developer provisioning credentials (if present)
JWT tokens are stateless, so no backend invalidation is required. Clearing cookies on client side immediately revokes access. The backend cannot track or revoke issued tokens before expiration.
Security Best Practices
Authentication Security:-
httpOnly Cookies for Token Storage
- Prevents XSS attacks (JavaScript cannot access tokens)
- Automatically sent with same-origin requests
- Protected from client-side tampering
- Implementation: All JWT tokens stored in httpOnly cookies via Next.js
cookies()API
-
CSRF Protection via SameSite
- All cookies use
sameSite: "lax"flag - Prevents cross-site request forgery attacks
- Cookies not sent with cross-origin POST requests
- Implementation: Set in cookie options for all auth cookies
- All cookies use
-
Return URL Validation
- Sanitize return URLs via
sanitizeReturnUrl()function - Only allow same-origin relative paths starting with
/ - Reject double-slash prefixes
//(open redirect vulnerability) - Maximum 2048 characters to prevent abuse
- Implementation: lib/return-url.ts with URL decoding and validation
- Sanitize return URLs via
-
Request Timeout Protection
- All fetch requests use AbortController with 10 second timeout
- Prevents hanging requests and resource exhaustion
- Implementation: AUTH_REQUEST_TIMEOUT constant in app/actions.ts
-
Password Hashing with bcrypt
- bcrypt work factor: 2^12 iterations (secure default)
- Passwords never logged or returned in responses
- Salt automatically generated per password
- Implementation: passlib.context.CryptContext in backend
-
Environment Variable Security
- API keys stored in server-side environment variables only
- Never exposed in client-side JavaScript or HTML
- Next.js NEXT_PUBLIC_ prefix only for non-sensitive URLs
- Implementation: .env.local file with strict access control
-
Immutable Event Log
- All user actions recorded as immutable events in event_store table
- Audit trail: UserWasRegistered, UserWasLoggedIn, DeveloperKeyWasGenerated
- Events never modified or deleted
- Implementation: EventSourcedRepository with append-only writes
-
Project-Scoped Access Control
- END_USER queries always include project_id filter
- Email uniqueness enforced per project
- Project ownership validated before user creation
- Implementation: Database unique constraint + backend validation
-
Rate Limit Auth Endpoints (Backend Responsibility)
- Prevent brute force attacks on login
- Limit registration attempts per IP
- Return 429 Too Many Requests status
- Implementation: FastAPI rate limiting middleware (if configured)
-
Monitor Failed Logins (Backend Responsibility)
- Track suspicious activity patterns
- Log failed authentication attempts
- Alert on repeated failures from same IP
- Implementation: Backend logging with UserWasLoggedIn event
-
Rotate API Keys Regularly
- Minimize exposure window if keys compromised
- Developer can revoke and regenerate keys via console
- Maximum 10 developer keys per developer (MAX_DEVELOPER_KEYS_PER_DEVELOPER)
- Implementation: Backend developer keys management endpoints
-
Email Verification Required
- New accounts start with
is_active: false - EmailVerificationWasRequested event emitted on registration
- 24 hour verification token expiry
- Implementation: Backend email verification flow (verification emails not yet sent)
- New accounts start with
Customization
Custom Registration Fields
Add additional fields to registration form beyond email, password, and full_name: Step 1: Extend Backend Request ModelCustom Redirect Logic
Modify post-login redirects based on custom business logic: Backend Role Resolution:Email Verification
Email verification infrastructure is in place with
is_active flags and EmailVerificationWasRequested events, but verification emails are not yet sent. This feature is planned for a future release.- All new users registered with
is_active: false - EmailVerificationWasRequested event emitted with 24h token
- Verification tokens stored in users table
- Manual activation required (update
is_activein database)
- User registers → Backend emits EmailVerificationWasRequested event
- Email service (future) sends verification email with link
- User clicks link → GET /api/v1/auth/verify-email?token=…
- Backend validates token → Updates
is_active: true→ Emits UserWasActivated event - User can now log in successfully
Related Pages
Protected Routes
Secure pages with authentication guards
Role-Based Access
Implement role-based permissions
JWT Flow
Understand token lifecycle
User Dashboard
Build authenticated user experiences

