POST /v1/auth/login
Email + password sign-in. May return either a full session (status: success)
or an MFA challenge (status: mfa_required).
::: tip Auth Required: none. :::
Request
POST /v1/auth/login
| Header | Required | Notes |
|---|---|---|
Content-Type: application/json | yes | — |
Cookie: identsphere_trust=... | no | Trusted-browser cookie, set by a prior POST /v1/auth/trusted-browsers. Lets a user skip MFA on a remembered device. |
Body
{
"email": "alice@example.com",
"password": "correct-horse-battery-staple"
}
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | Valid email. |
password | string | yes | 1+ chars (length checked, not enforced). |
Response
200 OK — status: "success"
Issued when MFA is not enabled, or a valid identsphere_trust cookie short-circuits
the challenge step.
{
"status": "success",
"user": { /* AuthUser, identical shape to /register */ },
"capabilities": { "role": "owner", "permissions": [...] },
"access_token": "eyJ...",
"expires_in": 900
}
Cookies set: identsphere_at, identsphere_rt, identsphere_csrf.
200 OK — status: "mfa_required"
Issued when the user has MFA enabled and the request did not include a valid trusted-browser cookie.
{
"status": "mfa_required",
"mfa_token": "eyJ...",
"mfa_token_expires_in": 300
}
No session cookies are set. The client must POST to
/v1/auth/mfa/challenge with the mfa_token plus a
TOTP code (or a recovery code) to obtain a session.
Error responses
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | Email format invalid. |
| 401 | authentication_required | User not found, wrong password, or account disabled/deleted. The message is intentionally constant — you cannot distinguish "no such user" from "wrong password". |
| 500 | internal_error | Database or hashing failure. |
Example: curl
curl -X POST https://auth.example.com/v1/auth/login \
-H 'Content-Type: application/json' \
-c cookies.txt \
-d '{"email":"alice@example.com","password":"correct-horse-battery-staple"}'
Example: TypeScript (@identsphere/react)
import { useLogin } from '@identsphere/react';
function LoginForm() {
const login = useLogin();
async function onSubmit(email: string, password: string) {
const res = await login.mutateAsync({ email, password });
if (res.status === 'mfa_required') {
// route to MFA challenge form
return navigate(`/mfa?token=${res.mfa_token}`);
}
// res.status === 'success' — user is signed in
navigate('/dashboard');
}
}
Notes
- Constant-message error: server returns identical bodies for "unknown email" and "wrong password" to defeat enumeration probes.
- Best-effort rehash: legacy bcrypt hashes are silently upgraded to Argon2id on successful login. Failures don't block the login.
- Audit: every login attempt — success or failure — emits an audit entry. Failed attempts include the attempted email in metadata.
- Trusted browsers: a valid
identsphere_trustcookie skips the MFA challenge AND upgrades the session to AAL2. - No password length minimum: legacy short passwords still work for login. The 12-char minimum applies only to NEW passwords (register / change / reset).