Multi-factor auth

Limen supports two second factors, configurable per tenant:

  • TOTP — a code from an authenticator app (Google Authenticator, 1Password, …).
  • Email OTP — a one-time code sent to the user's email.

How MFA surfaces at login

When a tenant requires MFA, the first login attempt with a correct password does not return a session. It returns 401 with a reason:

{
  "code": "UNAUTHORIZED",
  "message": "Multi-factor authentication required",
  "details": { "reason": "mfa_required" }
}

Collect the user's second factor and retry the same login request with the code included:

curl -X POST https://api.limen.eu/v1/acme/auth/login \
  -H "content-type: application/json" \
  -d '{
    "email": "you@acme.eu",
    "password": "a-strong-password",
    "totpCode": "123456"
  }'

Use emailOtpCode instead of totpCode when the factor is email.

Requesting an email code

For email OTP, ask Limen to send the code before the user can enter it:

curl -X POST https://api.limen.eu/v1/acme/auth/request-email-otp \
  -H "content-type: application/json" \
  -d '{ "email": "you@acme.eu" }'

The code is delivered by email and is valid for a short window.

Enrolling TOTP

A user with no factor yet enrolls TOTP in two steps — begin enrollment to get a secret and otpauth URL (render it as a QR code), then verify a code from the authenticator to activate it:

# 1. begin — returns the shared secret + otpauth:// URI
curl -X POST https://api.limen.eu/v1/acme/auth/totp/initial-enroll \
  -H "content-type: application/json" \
  -d '{ "email": "you@acme.eu", "password": "a-strong-password" }'

# 2. verify — activate the factor with a code from the app
curl -X POST https://api.limen.eu/v1/acme/auth/totp/initial-verify \
  -H "content-type: application/json" \
  -d '{ "email": "you@acme.eu", "password": "a-strong-password", "totpCode": "123456" }'
Secrets are encrypted at rest

TOTP secrets are stored with field-level AES-256-GCM encryption and key versioning — they are never persisted in plaintext.

The console does this for you

The Console's account page renders the QR code and handles enrollment end-to-end — useful for admins who don't want to build the enrollment UI themselves.