> ## 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.

# 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).

## Request

### Body

<ParamField body="email" type="string" required>
  User's email address
</ParamField>

<ParamField body="password" type="string" required>
  User's password (minimum 8 characters)
</ParamField>

### Headers

<ParamField header="X-Project-ID" type="string">
  **Required for end users only.** Project UUID for project-scoped authentication. Developers and operators do not provide this header.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

## Response

### Success Response (200 OK)

<ResponseField name="access_token" type="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).
</ResponseField>

<ResponseField name="refresh_token" type="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).
</ResponseField>

<ResponseField name="token_type" type="string">
  Token type (always "bearer")
</ResponseField>

## Example Requests

### End User Login (Project-Scoped)

End users must provide `X-Project-ID` header to authenticate within their specific project context:

```bash theme={null}
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:

```bash theme={null}
curl -X POST https://api.vibecoding.ad/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "developer@example.com",
    "password": "SecurePass123"
  }'
```

### Response

```json theme={null}
{
  "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:

```bash theme={null}
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`:

```bash theme={null}
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:
   ```python theme={null}
   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 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    |

**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:

```json theme={null}
{
  "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:**

```json theme={null}
{
  "WWW-Authenticate": "Bearer"
}
```

### Account Not Activated (400 Bad Request)

Returned when user exists but hasn't verified their email:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "detail": "Login failed: <error_message>"
}
```

**Common Causes:**

* Database connection failures
* Event store persistence errors
* JWT encoding failures

## Security Best Practices

<Warning>
  Always store tokens securely using httpOnly cookies. Never store tokens in localStorage or sessionStorage to prevent XSS attacks.
</Warning>

**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:**

```python theme={null}
{
    "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)))

## Related Pages

<CardGroup cols={2}>
  <Card title="Register" icon="user-plus" href="/cloud-api/auth/register">
    Create new user account
  </Card>

  <Card title="Get Current User" icon="user" href="/cloud-api/auth/me">
    Retrieve authenticated user details
  </Card>

  <Card title="Refresh Token" icon="arrows-rotate" href="/cloud-api/auth/refresh">
    Obtain new access token
  </Card>

  <Card title="Quick Start" icon="bolt" href="/cloud-api/quickstart">
    Complete authentication tutorial
  </Card>

  <Card title="JWT Flow Guide" icon="key" href="/starter-kit/auth/jwt-flow">
    Understanding token lifecycle
  </Card>

  <Card title="Protected Routes" icon="shield" href="/starter-kit/auth/protected-routes">
    Implement route protection
  </Card>
</CardGroup>


## OpenAPI

````yaml POST /api/v1/auth/login
openapi: 3.1.0
info:
  title: Dev Kit for AI API
  description: 'API for Dev Kit for AI: Developer Toolkit for AI-Powered Web Applications'
  version: 1.6.1
servers:
  - url: https://api.devkit4ai.com
    description: Dev Kit for AI Production Server
  - url: https://api.vibecoding.ad
    description: Vibe Coding Production Server
security: []
paths:
  /api/v1/auth/login:
    post:
      tags:
        - Authentication
      summary: Login
      operationId: login_api_v1_auth_login_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserLoginRequest'
        required: true
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    UserLoginRequest:
      properties:
        email:
          type: string
          format: email
          title: Email
        password:
          type: string
          title: Password
      type: object
      required:
        - email
        - password
      title: UserLoginRequest
    TokenResponse:
      properties:
        access_token:
          type: string
          title: Access Token
        refresh_token:
          anyOf:
            - type: string
            - type: 'null'
          title: Refresh Token
        token_type:
          type: string
          title: Token Type
      type: object
      required:
        - access_token
        - token_type
      title: TokenResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError

````