Skip to main content

POST /v1/auth/mfa/verify

Stamp a "recently MFA-verified" assertion on the caller's session. The step-up middleware reads this back on subsequent sensitive operations to confirm the user proved a second factor recently.

::: tip Auth Required: cookie or Bearer. Must be a browser session (not an API key). :::

Request

POST /v1/auth/mfa/verify

HeaderRequiredNotes
Cookie: identsphere_at=... OR Authorization: Bearer ...yes
Content-Type: application/jsonyes

Body

{ "code": "123456" }
FieldTypeRequiredNotes
codestringyes6–8 chars. The current TOTP code. Recovery codes are NOT accepted here — they're for account recovery at sign-in time.

Response

200 OK

{
"verified": true,
"verified_at": 1748448000,
"expires_in": 1800
}
FieldTypeNotes
verifiedboolAlways true on 2xx.
verified_atintUnix timestamp at which verification was recorded.
expires_inintSeconds until the step-up assertion expires (default 1800, configurable via step_up_ttl_secs).

Error responses

StatusCodeWhen
400invalid_inputCode missing or wrong length.
401authentication_requiredCode doesn't validate against the stored TOTP secret.
403forbiddenCaller is not a browser session (e.g. API key), OR MFA isn't enabled for this user.
404not_foundUser no longer exists.
500internal_errorTOTP build or storage failure.

Example: curl

curl -X POST https://auth.example.com/v1/auth/mfa/verify \
-H 'Content-Type: application/json' \
-b cookies.txt \
-d '{"code":"123456"}'

Example: TypeScript (@identsphere/react)

import { useMfaVerify } from '@identsphere/react';

function StepUpModal({ onSuccess }: { onSuccess: () => void }) {
const verify = useMfaVerify();
return (
<form onSubmit={async (e) => {
e.preventDefault();
const code = new FormData(e.currentTarget).get('code') as string;
await verify.mutateAsync({ code });
onSuccess();
}}>
<input name="code" placeholder="123456" />
<button type="submit">Verify</button>
</form>
);
}

Notes

  • This endpoint is for step-up authentication — proving the user is still in front of the screen before a sensitive operation. It is NOT the same as the initial login MFA challenge (that's /v1/auth/mfa/challenge).
  • The assertion is keyed by session_family_id. Other sessions belonging to the same user are unaffected.
  • An audit entry (auth.mfa.step_up) is recorded.
  • The default step-up TTL is 30 minutes; tune it via AppConfig::step_up_ttl_secs.