Repository navigation
Authentication Flow
Canonical source:
docs/03-architecture/authentication-flow.md— this page is rendered from it on every publish; edit the source.
ClawAI uses a dual-token authentication scheme with JWT access tokens and refresh token rotation. Passwords are hashed with argon2id. Sessions are tracked in PostgreSQL with IP and user agent metadata.
sequenceDiagram
participant Client as Frontend
participant Nginx as Nginx (4000)
participant Auth as Auth Service (4001)
participant DB as PostgreSQL (claw_auth)
Client->>Nginx: POST /api/v1/auth/login {email, password}
Nginx->>Auth: Forward request
Auth->>DB: Find user by email
DB-->>Auth: User record (passwordHash, role, status)
Auth->>Auth: argon2.verify(password, passwordHash)
alt Password invalid
Auth-->>Client: 401 Unauthorized
end
alt Account inactive
Auth-->>Client: 403 Account disabled
end
Auth->>Auth: Generate JWT access token
Auth->>Auth: Generate refresh token (random UUID)
Auth->>DB: Create Session {userId, refreshToken, expiresAt, IP, UA}
Auth-->>Client: 200 {accessToken, refreshToken, user}
Note over Client: Store tokens in Zustand store
Client->>Auth: Publish user.login event to RabbitMQ
{
"sub": "user-uuid",
"email": "user@example.com",
"role": "OPERATOR",
"sessionId": "session-uuid",
"iat": 1712800000,
"exp": 1712800900
}| Token | Default | Environment Variable |
|---|---|---|
| Access Token | 15 minutes | JWT_ACCESS_EXPIRY |
| Refresh Token | 7 days | JWT_REFRESH_EXPIRY |
sequenceDiagram
participant Client as Frontend
participant Auth as Auth Service
participant DB as PostgreSQL
Client->>Auth: POST /api/v1/auth/refresh {refreshToken}
Auth->>DB: Find session by refreshToken
alt Session not found
Auth-->>Client: 401 Invalid refresh token
end
alt Session expired
Auth->>DB: Delete expired session
Auth-->>Client: 401 Session expired
end
Auth->>Auth: Generate new access token
Auth->>Auth: Generate new refresh token
Auth->>DB: Update session (new refreshToken, new expiresAt)
Auth-->>Client: 200 {accessToken (new), refreshToken (new)}
- Single-use tokens: Each refresh token can only be used once. After rotation, the old token is invalidated.
- Theft detection: If an attacker steals and uses a refresh token, the legitimate user's next refresh attempt fails (token already rotated). This signals a compromised session.
- No replay: Old refresh tokens cannot be replayed after rotation.
- Session binding: Refresh tokens are tied to a specific session record in the database.
sequenceDiagram
participant Client as Frontend
participant Auth as Auth Service
participant DB as PostgreSQL
Client->>Auth: POST /api/v1/auth/logout
Note over Client: Authorization: Bearer <accessToken>
Auth->>Auth: Extract sessionId from JWT
Auth->>DB: Delete session by sessionId
Auth-->>Client: 200 OK
Auth->>Auth: Publish user.logout event
Note over Client: Clear tokens from Zustand store
Session {
id: UUID
userId: UUID
refreshToken: String (hashed)
expiresAt: DateTime
ipAddress: String
userAgent: String
createdAt: DateTime
updatedAt: DateTime
}
| Operation | Endpoint | Access |
|---|---|---|
| List sessions | GET /api/v1/auth/sessions | ADMIN (all), OPERATOR (own) |
| Revoke session | DELETE /api/v1/auth/sessions/:id | ADMIN (any), OPERATOR (own) |
| Revoke all | DELETE /api/v1/auth/sessions | ADMIN only (force logout all) |
- Expired sessions are cleaned up periodically
- Logout deletes the session immediately
- Revoking a session invalidates its refresh token
- Memory cost: Default argon2 parameters (memory-hard)
- Time cost: Default iterations
- Parallelism: Default threads
- Resistance: GPU/ASIC attack resistant due to memory-hardness
Enforced via Zod schema at registration and password change:
- Minimum length (configurable)
- No plaintext storage at any point
- No plaintext logging (Pino redaction covers
passwordfield) - No plaintext transmission (HTTPS in production)
Currently stored in Zustand store (in-memory) with localStorage persistence:
// auth.store.ts (Zustand)
{
accessToken: string | null
refreshToken: string | null
user: User | null
}Known Issue (TD-008): localStorage is vulnerable to XSS. Planned migration to httpOnly cookies.
The HTTP client interceptor (http-client.ts) handles 401 responses:
- Intercept 401 response
- Attempt token refresh (POST /auth/refresh)
- If refresh succeeds: retry the original request with new token
- If refresh fails: redirect to login page
-
EventSourceAPI cannot set Authorization headers - ClawAI uses
fetch()withReadableStreamfor SSE connections - Authorization header is set on the fetch request
- Token refresh is NOT automatic for SSE connections (requires reconnection)
1. Request arrives at NestJS service
2. AuthGuard (global) checks for @Public() decorator
-> If @Public(): skip authentication
3. Extract JWT from Authorization: Bearer <token>
-> If missing: 401 Unauthorized
4. Verify JWT signature using JWT_SECRET
-> If invalid: 401 Unauthorized
5. Check token expiry
-> If expired: 401 Unauthorized
6. Decode payload, attach user to request.user
7. RolesGuard checks @Roles() decorator (if present)
-> If role not allowed: 403 Forbidden
8. Request proceeds to controller
Every authentication event is published to RabbitMQ and consumed by the audit service:
| Event | Trigger | Payload |
|---|---|---|
user.login |
Successful login | userId, email, role, sessionId, IP, userAgent |
user.logout |
Logout or session revocation | userId, sessionId, reason |
Failed login attempts are logged but not published as events (to prevent event flooding during brute-force attacks).
ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference