Managing external identities to enable secure access for partners, customers, and other non-employees
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
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)
-
POST /resetpassword/v1.0/startwith the user'susername→ continuation_token. -
POST /resetpassword/v1.0/challengewith that continuation_token → Entra emails an 8-digit OTP (call it CODE-A). The reset flow is abandoned here —continue/submitare never called, the user's password is never changed. - Independently, start a normal sign-in:
POST /oauth2/v2.0/initiate→POST /oauth2/v2.0/challenge(challenge_type=password oob redirect,capabilities=mfa_required registration_required) →POST /oauth2/v2.0/token(grant_type=password, correct password) → returnserror=invalid_grant,suberror=mfa_required(AADSTS50074), a new, unrelated continuation_token. -
POST /oauth2/v2.0/introspectwith that continuation_token → returns the registered method id. -
POST /oauth2/v2.0/challenge(challenge_type=oob,id=<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. -
POST /oauth2/v2.0/tokenwithgrant_type=mfa_oob, this sign-in continuation_token, and CODE-A (the password-reset code from step 2), plusscope.
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-id:c59d3eab-fffd-4dcc-91f6-50d0ac350200 -
x-ms-ests-server:2.1.25181.4 - FRC ProdSlices - For reference, the preceding
mfa_requirederror (step 3) at2026-09-01 15:22:47Zhadcorrelation_id: 4bb99295-5bc2-4571-81fd-637802260d48,trace_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)
-
POST /resetpassword/v1.0/startwith the user'susername→ continuation_token. -
POST /resetpassword/v1.0/challengewith that continuation_token → Entra emails an 8-digit OTP (call it CODE-A). The reset flow is abandoned here —continue/submitare never called, the user's password is never changed. - Independently, start a normal sign-in:
POST /oauth2/v2.0/initiate→POST /oauth2/v2.0/challenge(challenge_type=password oob redirect,capabilities=mfa_required registration_required) →POST /oauth2/v2.0/token(grant_type=password, correct password) → returnserror=invalid_grant,suberror=mfa_required(AADSTS50074), a new, unrelated continuation_token. -
POST /oauth2/v2.0/introspectwith that continuation_token → returns the registered method id. -
POST /oauth2/v2.0/challenge(challenge_type=oob,id=<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. -
POST /oauth2/v2.0/tokenwithgrant_type=mfa_oob, this sign-in continuation_token, and CODE-A (the password-reset code from step 2), plusscope.
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-id:c59d3eab-fffd-4dcc-91f6-50d0ac350200 -
x-ms-ests-server:2.1.25181.4 - FRC ProdSlices - For reference, the preceding
mfa_requirederror (step 3) at2026-09-01 15:22:47Zhadcorrelation_id: 4bb99295-5bc2-4571-81fd-637802260d48,trace_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.