Native authentication: an email OTP issued by resetpassword/v1.0/challenge is accepted as a valid mfa_oob code on an unrelated oauth2/v2.0 sign-in MFA continuation token

Aleksandr Mulgachev 0 Reputation points
2026-09-01T15:33:19.05+00:00

Related to the already-acknowledged bug in this thread (resend not invalidating the previous OTP within the same flow). We found a more severe variant: an OTP code issued by a completely different flow (self-service password reset) is accepted to satisfy a sign-in MFA challenge, on a continuation_token that has no relation to the reset flow whatsoever.

Environment

  • Native authentication, External ID (CIAM) tenant.
  • Tenant: ca459b94-c28a-466c-8643-ef1934f0ba10 (rockatest.ciamlogin.com)
  • Client ID (public/native client): afbec61a-53b4-49d1-8a3a-7b4c49781b62
  • User has Email OTP registered as their MFA method (sign-in requires mfa_required/AADSTS50074 on password grant).

Repro steps (all calls direct to the tenant, bypassing our own app code)

  1. POST /resetpassword/v1.0/start with the user's username → continuation_token.
  2. POST /resetpassword/v1.0/challenge with that continuation_token → Entra emails an 8-digit OTP (call it CODE-A). The reset flow is abandoned here — continue/submit are never called, the user's password is never changed.
  3. Independently, start a normal sign-in: POST /oauth2/v2.0/initiate → POST /oauth2/v2.0/challenge (challenge_type=password oob redirectcapabilities=mfa_required registration_required) → POST /oauth2/v2.0/token (grant_type=password, correct password) → returns error=invalid_grantsuberror=mfa_required (AADSTS50074), a new, unrelated continuation_token.
  4. POST /oauth2/v2.0/introspect with that continuation_token → returns the registered method id.
  5. POST /oauth2/v2.0/challenge (challenge_type=oobid=<method id>) → Entra emails a second, independent 8-digit OTP (call it CODE-B) and returns a fresh continuation_token for this sign-in MFA round. CODE-B is never used.
  6. POST /oauth2/v2.0/token with grant_type=mfa_oob, this sign-in continuation_token, and CODE-A (the password-reset code from step 2), plus scope.

Actual result

Step 6 returns HTTP 200 with a full, valid access_token/refresh_token/id_token for the user — i.e. the password-reset OTP is accepted as the MFA second factor for an entirely unrelated sign-in continuation_token.

  • Timestamp (UTC): 2026-09-01 15:26:28Z
  • x-ms-request-idc59d3eab-fffd-4dcc-91f6-50d0ac350200
  • x-ms-ests-server2.1.25181.4 - FRC ProdSlices
  • For reference, the preceding mfa_required error (step 3) at 2026-09-01 15:22:47Z had correlation_id: 4bb99295-5bc2-4571-81fd-637802260d48trace_id: b0a247c2-0b97-4518-98d3-edff0cc20200.

Expected result

Step 6 should fail (invalid/expired code) — the OTP from an unrelated flow/continuation_token should not satisfy a different flow's MFA challenge.

Why this matters

This isn't just "resend doesn't invalidate the old code within one flow" (already tracked in the linked thread) — it suggests Entra's OTP validation for a given identity+method isn't scoped to the continuation_token/flow at all, but to a single shared "current valid code(s) for this user's email OTP method," regardless of which native-auth flow requested it (SSPR vs sign-in MFA vs presumably registration). That undermines the assumption that a continuation_token meaningfully scopes which code is acceptable.

Is this the same root cause as the resend issue, or a distinct bug? Any guidance on tracking/escalation for this specific cross-flow case would be appreciated. Related to the already-acknowledged bug in this thread (resend not invalidating the previous OTP within the same flow). We found a more severe variant: an OTP code issued by a completely different flow (self-service password reset) is accepted to satisfy a sign-in MFA challenge, on a continuation_token that has no relation to the reset flow whatsoever.

Environment

  • Native authentication, External ID (CIAM) tenant.
  • Tenant: ca459b94-c28a-466c-8643-ef1934f0ba10 (rockatest.ciamlogin.com)
  • Client ID (public/native client): afbec61a-53b4-49d1-8a3a-7b4c49781b62
  • User has Email OTP registered as their MFA method (sign-in requires mfa_required/AADSTS50074 on password grant).

Repro steps (all calls direct to the tenant, bypassing our own app code)

  1. POST /resetpassword/v1.0/start with the user's username → continuation_token.
  2. POST /resetpassword/v1.0/challenge with that continuation_token → Entra emails an 8-digit OTP (call it CODE-A). The reset flow is abandoned here — continue/submit are never called, the user's password is never changed.
  3. Independently, start a normal sign-in: POST /oauth2/v2.0/initiate → POST /oauth2/v2.0/challenge (challenge_type=password oob redirectcapabilities=mfa_required registration_required) → POST /oauth2/v2.0/token (grant_type=password, correct password) → returns error=invalid_grantsuberror=mfa_required (AADSTS50074), a new, unrelated continuation_token.
  4. POST /oauth2/v2.0/introspect with that continuation_token → returns the registered method id.
  5. POST /oauth2/v2.0/challenge (challenge_type=oobid=<method id>) → Entra emails a second, independent 8-digit OTP (call it CODE-B) and returns a fresh continuation_token for this sign-in MFA round. CODE-B is never used.
  6. POST /oauth2/v2.0/token with grant_type=mfa_oob, this sign-in continuation_token, and CODE-A (the password-reset code from step 2), plus scope.

Actual result

Step 6 returns HTTP 200 with a full, valid access_token/refresh_token/id_token for the user — i.e. the password-reset OTP is accepted as the MFA second factor for an entirely unrelated sign-in continuation_token.

  • Timestamp (UTC): 2026-09-01 15:26:28Z
  • x-ms-request-idc59d3eab-fffd-4dcc-91f6-50d0ac350200
  • x-ms-ests-server2.1.25181.4 - FRC ProdSlices
  • For reference, the preceding mfa_required error (step 3) at 2026-09-01 15:22:47Z had correlation_id: 4bb99295-5bc2-4571-81fd-637802260d48trace_id: b0a247c2-0b97-4518-98d3-edff0cc20200.

Expected result

Step 6 should fail (invalid/expired code) — the OTP from an unrelated flow/continuation_token should not satisfy a different flow's MFA challenge.

Why this matters

This isn't just "resend doesn't invalidate the old code within one flow" (already tracked in the linked thread) — it suggests Entra's OTP validation for a given identity+method isn't scoped to the continuation_token/flow at all, but to a single shared "current valid code(s) for this user's email OTP method," regardless of which native-auth flow requested it (SSPR vs sign-in MFA vs presumably registration). That undermines the assumption that a continuation_token meaningfully scopes which code is acceptable.

Is this the same root cause as the resend issue, or a distinct bug? Any guidance on tracking/escalation for this specific cross-flow case would be appreciated.

Microsoft Security | Microsoft Entra | Microsoft Entra External ID
0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.