Endpoint
POST /api/v1/auth/login
This endpoint is public (no token required).
Request payload
Required fields
These fields are always required and validated strictly.-
email(string)
User email address. Normalized (lowercased, trimmed) server-side. -
password(string)
User password. -
device_id(string)
Stable, unique identifier for the device.
Mandatory to enforce one-session-per-device. -
device_type(string)
Device category (e.g.ios,android,web). -
device_name(string)
Human-readable name shown in device/session listings.
Optional fields
country(string)
Optional metadata, stored with the session token.
Localization
Before any authentication logic runs, locale is resolved using:X-App-LocaleAccept-Languageuser.locale(if authenticated)- fallback:
fr
- response
message - validation errors
- authentication errors
code field is never localized.
Login outcomes
There are two possible outcomes: direct token issuance or a 2FA challenge.Case 1: Login success (2FA not enabled)
If the user does not have 2FA enabled:- Credentials are verified
- Any existing token for the same
device_idis revoked - A new token is issued atomically
- exactly one token per
device_id - other devices remain logged in
- token issuance is transactional
Case 2: 2FA required
If the user has 2FA enabled, no token is issued at this stage. Instead, the API returns a login challenge.- stored in cache
- bound to:
- IP address
- User-Agent
device_id
- hard expiration (TTL is never extended)
- max 5 verification attempts
Verify login with 2FA (second step)
Endpoint
POST /api/v1/auth/2fa/verify-login
Required fields
-
challenge_id(UUID)
Identifier returned by the login step. -
code(string)
TOTP code from the authenticator app.
Verification rules
- strict IP match
- strict User-Agent match
- maximum 5 attempts per challenge
- immediate invalidation on expiration
- no TTL extension on retries
Successful verification response
Anti-enumeration guarantees
The login endpoint never reveals whether:- the email exists
- the password is incorrect
- the account is inactive or blocked
- HTTP
401 code: INVALID_CREDENTIALS
Rate limiting
Login attempts are rate-limited peremail + ip.
- attempts are counted even if the user does not exist
- limits apply before password verification
- repeated failures result in
RATE_LIMITED(429)
Token issuance policy
When a token is issued:- 1 active token per
device_id - previous tokens for the same device are deleted before creation
- advisory locks (PostgreSQL) prevent concurrent duplication
Common frontend mistakes to avoid
- Treating
device_idas optional - Assuming login always returns a token
- Storing
challenge_idlong-term - Retrying 2FA verification after expiration
- Using
messageinstead ofcodefor logic
Summary
The login endpoint provides:- deterministic authentication behavior
- strong anti-enumeration guarantees
- device-level session isolation
- first-class 2FA enforcement
- fully localized responses

