Skip to main content
POST
/
api
/
v1
/
auth
/
login
Login
curl --request POST \
  --url https://api.devkit4ai.com/api/v1/auth/login \
  --header 'Content-Type: application/json' \
  --data '
{
  "email": "jsmith@example.com",
  "password": "<string>"
}
'
{
  "access_token": "<string>",
  "token_type": "<string>",
  "refresh_token": "<string>"
}
Authenticate a user and receive access and refresh tokens. Supports both global login (for developers and operators) and project-scoped login (for end users).

Request

Body

email
string
required
User’s email address
password
string
required
User’s password (minimum 8 characters)

Headers

X-Project-ID
string
Required for end users only. Project UUID for project-scoped authentication. Developers and operators do not provide this header.
Content-Type
string
required
Must be application/json

Response

Success Response (200 OK)

access_token
string
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
string
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
string
Token type (always “bearer”)

Example Requests

End User Login (Project-Scoped)

End users must provide X-Project-ID header to authenticate within their specific project context:
curl -X POST https://api.vibecoding.ad/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "X-Project-ID: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePass123"
  }'

Developer/Operator Login (Global)

Developers and operators authenticate globally without project context:
curl -X POST https://api.vibecoding.ad/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "developer@example.com",
    "password": "SecurePass123"
  }'

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer"
}

Backend Implementation Flow

  1. Header Validation: Extract optional X-Project-ID header and validate UUID format
  2. Command Creation: LoginUserCommand created with email, password, and optional project_id
  3. User Lookup:
    • If project_id provided: 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
  4. Role Validation: For project-scoped login, validates user role is END_USER
  5. Aggregate Load: LoginUserHandler loads user aggregate from event store via repository.get_by_id_or_raise()
  6. Password Verification: UserActions.login() validates:
    • Hashed password exists
    • Password matches via pwd_context.verify() (bcrypt)
    • Raises ValueError("Invalid password") if verification fails
  7. Event Emission: UserWasLoggedIn event raised with user_id and email
  8. Event Persistence: Event saved to event store and published to PubSub
  9. Token Generation:
    • Access token: 30 minutes expiry, contains sub, type: "access", exp, and project_id (for end users)
    • Refresh token: 7 days expiry, contains sub, type: "refresh", exp, and project_id (for end users)
    • Algorithm: HS256
    • Secret: settings.SECRET_KEY
  10. Response: Returns TokenResponse with both tokens and type “bearer”

Token Usage

Access Token

Use the access token in the Authorization header for subsequent API requests:
curl https://api.vibecoding.ad/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Properties:
  • Expiry: 30 minutes (configurable via settings.ACCESS_TOKEN_EXPIRE_MINUTES)
  • Algorithm: HS256
  • Claims:
    • sub: User ID (UUID string)
    • type: “access”
    • exp: Expiration timestamp
    • project_id: Project UUID (included only for end users)
  • Purpose: Authenticate API requests
  • Validation: Via get_current_user() dependency using jwt.decode()

Refresh Token

Use the refresh token to obtain a new access token via /api/v1/auth/refresh:
curl -X POST https://api.vibecoding.ad/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}'
Properties:
  • Expiry: 7 days
  • Algorithm: HS256
  • Claims:
    • sub: User ID (UUID string)
    • type: “refresh”
    • exp: Expiration timestamp
    • project_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 the X-Project-ID header:

Authentication Flow

  1. Registration: End user registered with X-Project-ID header, user record includes project_id field in database
  2. Login Request: Must provide same X-Project-ID header matching registered project
  3. User Lookup: Backend queries users table filtering by email AND project_id:
    UserService.get_user_by_email_and_project(email, project_id)
    
  4. Role Validation: Verifies user role is END_USER (raises ValueError if not)
  5. JWT Token: Generated with project_id claim for authorization
  6. API Access: All subsequent requests scoped to user’s project via JWT project_id claim

Email Uniqueness Model

The same email can exist as multiple user types due to project scoping:
User TypeProject IDNamespace
End user in Project AUUIDProject A
End user in Project BUUIDProject B
DeveloperNULLGlobal
OperatorNULLGlobal
Database Implementation:
  • Unique constraint: email + project_id (allows same email across different projects)
  • Partial unique index: Allows NULL project_id for operators/developers with same email as end users
  • Query logic uses X-Project-ID header 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
Missing Project ID:
  • 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:
{
  "detail": "Incorrect email or password"
}
Triggered by:
  • ValueError("Invalid email or password") from LoginUserHandler
  • ValueError("Invalid password") from UserActions.login() when bcrypt verification fails
  • User not found in database lookup
Headers:
{
  "WWW-Authenticate": "Bearer"
}

Account Not Activated (400 Bad Request)

Returned when user exists but hasn’t verified their email:
{
  "detail": "Account not activated"
}
Triggered by:
  • ValueError containing “not active” from aggregate validation
  • User is_active field is false

Invalid Project ID Format (400 Bad Request)

Returned when X-Project-ID header is provided but not a valid UUID:
{
  "detail": "Invalid X-Project-ID format. Must be a valid UUID."
}
Triggered by:
  • uuid.UUID(project_id_header) raises ValueError in endpoint
  • Example invalid values: “not-a-uuid”, “123”, ""

Internal Server Error (500)

Returned for unexpected errors during login:
{
  "detail": "Login failed: <error_message>"
}
Common Causes:
  • Database connection failures
  • Event store persistence errors
  • JWT encoding failures

Security Best Practices

Always store tokens securely using httpOnly cookies. Never store tokens in localStorage or sessionStorage to prevent XSS attacks.
Implementation Recommendations:
  1. Token Storage: Use httpOnly cookies with secure flag in production
    • Frontend: storeTokensInCookies() helper sets cookies with httpOnly: 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)
  2. 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
  3. 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
  4. Logout: Clear both access and refresh tokens on logout
    • Delete cookies: clearTokensFromCookies() helper
    • Backend doesn’t maintain session state (stateless JWT)
  5. Rate Limiting: Implement rate limiting to prevent brute force attacks
    • Recommended: 5 attempts per 15 minutes per IP
    • Consider CAPTCHA after 3 failed attempts
  6. Password Security:
    • Backend uses bcrypt for password hashing via pwd_context.verify()
    • Minimum 8 characters enforced during registration
    • Recommend requiring uppercase, lowercase, and digit
  7. 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:
{
    "user_id": str(UUID),
    "email": str,
    "aggregate_id": str(UUID),
    "event_type": "UserWasLoggedIn",
    "timestamp": datetime
}
Event Flow:
  1. Event raised in UserActions.login() aggregate method
  2. Persisted to event_store table via EventSourcedRepository
  3. Published to PubSub (Redis or in-memory)
  4. Can be consumed by subscribers for:
    • Login analytics
    • Security monitoring
    • Audit logging
    • User behavior tracking
(((REPLACE_THIS_WITH_IMAGE: cloud-api-login-jwt-flow.png: Sequence diagram showing login flow from credentials submission through JWT token generation and API usage)))

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

Body

application/json
email
string<email>
required
password
string
required

Response

Successful Response

access_token
string
required
token_type
string
required
refresh_token
string | null