Skip to main content

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

HeaderRequiredNotes
Content-Type: application/jsonyes
Cookie: identsphere_trust=...noTrusted-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"
}
FieldTypeRequiredNotes
emailstringyesValid email.
passwordstringyes1+ 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

StatusCodeWhen
400invalid_inputEmail format invalid.
401authentication_requiredUser not found, wrong password, or account disabled/deleted. The message is intentionally constant — you cannot distinguish "no such user" from "wrong password".
500internal_errorDatabase 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_trust cookie 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).