Login
Authentication
Login
POST
Login
Authenticate a user and receive access and refresh tokens. Supports both global login (for developers and operators) and project-scoped login (for end users).Documentation Index
Fetch the complete documentation index at: https://devkit4ai.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Request
Body
User’s email address
User’s password (minimum 8 characters)
Headers
Required for end users only. Project UUID for project-scoped authentication. Developers and operators do not provide this header.
Must be
application/jsonResponse
Success Response (200 OK)
JWT access token for API authentication. Expires in 30 minutes (configurable via
ACCESS_TOKEN_EXPIRE_MINUTES). Contains claims: sub (user_id), type (“access”), exp (expiration), and project_id (for end users only).Refresh token to obtain new access tokens. Expires in 7 days. Contains claims:
sub (user_id), type (“refresh”), exp (expiration), and project_id (for end users only).Token type (always “bearer”)
Example Requests
End User Login (Project-Scoped)
End users must provideX-Project-ID header to authenticate within their specific project context:
Developer/Operator Login (Global)
Developers and operators authenticate globally without project context:Response
Backend Implementation Flow
- Header Validation: Extract optional
X-Project-IDheader and validate UUID format - Command Creation:
LoginUserCommandcreated with email, password, and optional project_id - User Lookup:
- If
project_idprovided:UserService.get_user_by_email_and_project()queries for end user - If no
project_id:UserService.get_user_id_by_email()queries for operator/developer
- If
- Role Validation: For project-scoped login, validates user role is END_USER
- Aggregate Load:
LoginUserHandlerloads user aggregate from event store viarepository.get_by_id_or_raise() - Password Verification:
UserActions.login()validates:- Hashed password exists
- Password matches via
pwd_context.verify()(bcrypt) - Raises
ValueError("Invalid password")if verification fails
- Event Emission:
UserWasLoggedInevent raised with user_id and email - Event Persistence: Event saved to event store and published to PubSub
- Token Generation:
- Access token: 30 minutes expiry, contains
sub,type: "access",exp, andproject_id(for end users) - Refresh token: 7 days expiry, contains
sub,type: "refresh",exp, andproject_id(for end users) - Algorithm: HS256
- Secret:
settings.SECRET_KEY
- Access token: 30 minutes expiry, contains
- Response: Returns
TokenResponsewith both tokens and type “bearer”
Token Usage
Access Token
Use the access token in theAuthorization header for subsequent API requests:
- Expiry: 30 minutes (configurable via
settings.ACCESS_TOKEN_EXPIRE_MINUTES) - Algorithm: HS256
- Claims:
sub: User ID (UUID string)type: “access”exp: Expiration timestampproject_id: Project UUID (included only for end users)
- Purpose: Authenticate API requests
- Validation: Via
get_current_user()dependency usingjwt.decode()
Refresh Token
Use the refresh token to obtain a new access token via/api/v1/auth/refresh:
- Expiry: 7 days
- Algorithm: HS256
- Claims:
sub: User ID (UUID string)type: “refresh”exp: Expiration timestampproject_id: Project UUID (included only for end users)
- Storage: httpOnly cookies recommended
- Security: Single-use pattern recommended
Project-Scoped Authentication
End users are authenticated within the context of a specific project using theX-Project-ID header:
Authentication Flow
- Registration: End user registered with
X-Project-IDheader, user record includesproject_idfield in database - Login Request: Must provide same
X-Project-IDheader matching registered project - User Lookup: Backend queries
userstable filtering by email AND project_id: - Role Validation: Verifies user role is END_USER (raises ValueError if not)
- JWT Token: Generated with
project_idclaim for authorization - API Access: All subsequent requests scoped to user’s project via JWT
project_idclaim
Email Uniqueness Model
The same email can exist as multiple user types due to project scoping:| User Type | Project ID | Namespace |
|---|---|---|
| End user in Project A | UUID | Project A |
| End user in Project B | UUID | Project B |
| Developer | NULL | Global |
| Operator | NULL | Global |
- Unique constraint:
email + project_id(allows same email across different projects) - Partial unique index: Allows NULL
project_idfor operators/developers with same email as end users - Query logic uses
X-Project-IDheader to determine which account to authenticate
Project ID Validation
Format Requirements:- Must be valid UUID format
- Validated via
uuid.UUID(project_id_header)in endpoint - Returns 400 Bad Request with message “Invalid X-Project-ID format. Must be a valid UUID.” if invalid
- End users attempting global login (without
X-Project-ID) will fail with “Invalid email or password” - System performs user lookup that requires project_id match, preventing cross-project access
Error Responses
Invalid Credentials (401 Unauthorized)
Returned when email or password is incorrect, or user not found:ValueError("Invalid email or password")fromLoginUserHandlerValueError("Invalid password")fromUserActions.login()when bcrypt verification fails- User not found in database lookup
Account Not Activated (400 Bad Request)
Returned when user exists but hasn’t verified their email:ValueErrorcontaining “not active” from aggregate validation- User
is_activefield is false
Invalid Project ID Format (400 Bad Request)
Returned whenX-Project-ID header is provided but not a valid UUID:
uuid.UUID(project_id_header)raisesValueErrorin endpoint- Example invalid values: “not-a-uuid”, “123”, ""
Internal Server Error (500)
Returned for unexpected errors during login:- Database connection failures
- Event store persistence errors
- JWT encoding failures
Security Best Practices
Implementation Recommendations:-
Token Storage: Use httpOnly cookies with secure flag in production
- Frontend:
storeTokensInCookies()helper sets cookies withhttpOnly: true,secure: <protocol-based>,sameSite: 'lax' - Cookie names:
devkit4ai-token(access),devkit4ai-refresh-token(refresh) - Expiry: Matches token expiry (30 min for access, 7 days for refresh)
- Frontend:
-
HTTPS Only: Always use HTTPS in production to protect tokens in transit
- Secure flag automatically enabled when protocol is HTTPS
- Local development (localhost) excluded from secure requirement
-
Token Refresh: Implement automatic token refresh before expiry
- Access token expires in 30 minutes
- Refresh endpoint:
POST /api/v1/auth/refresh - Client should refresh ~5 minutes before expiry
-
Logout: Clear both access and refresh tokens on logout
- Delete cookies:
clearTokensFromCookies()helper - Backend doesn’t maintain session state (stateless JWT)
- Delete cookies:
-
Rate Limiting: Implement rate limiting to prevent brute force attacks
- Recommended: 5 attempts per 15 minutes per IP
- Consider CAPTCHA after 3 failed attempts
-
Password Security:
- Backend uses bcrypt for password hashing via
pwd_context.verify() - Minimum 8 characters enforced during registration
- Recommend requiring uppercase, lowercase, and digit
- Backend uses bcrypt for password hashing via
-
Error Messages: Generic “Incorrect email or password” to prevent user enumeration
- Same message for invalid email, invalid password, or inactive account in some cases
- Don’t reveal whether email exists in system
Event Sourcing
Login operations emit domain events for audit trail and analytics: Event Type:UserWasLoggedIn
Event Data:
- Event raised in
UserActions.login()aggregate method - Persisted to
event_storetable viaEventSourcedRepository - Published to PubSub (Redis or in-memory)
- Can be consumed by subscribers for:
- Login analytics
- Security monitoring
- Audit logging
- User behavior tracking
Related Pages
Register
Create new user account
Get Current User
Retrieve authenticated user details
Refresh Token
Obtain new access token
Quick Start
Complete authentication tutorial
JWT Flow Guide
Understanding token lifecycle
Protected Routes
Implement route protection

