Skip to content

Linking Microsoft account via Oauth requires MFA twice (before and after link) #8675

@densk1

Description

@densk1

Operating System

Sonoma 14.5

Environment (if applicable)

Chrome Version 131

Firebase SDK Version

firebase@11.0.2

Firebase SDK Product(s)

Auth

Project Tooling

React App (18.2.0)
Webpack

Detailed Problem Description

Our application is built with email/password (+ optional MFA) auth. Our customers have long requested SSO login with Microsoft.

In the process of building out the UI and flows, the process will fail when MFA was enabled on the base (email auth) account.

Digging into the error, it fails because Firebase is requesting MFA re-authentication for a second time after linkWithPopup has completed.

To be clear I've already successfully re-authenticated with password/MFA before calling linkWithPopup.

  1. In the Firebase auth UI I can see the account is already linked
  2. If refresh the tab, the account will then be linked without needing to go through the second MFA flow.

Workaround
When the second MFA error is thrown, the page is hard refreshed so the user doesn't need to go through the MFA flow a second time.

Given that you can refresh to avoid the second MFA flow, this does not add any extra security


This works with no issue when using single factor authentication on the email auth account.

Steps and code to reproduce issue

Error

ConnectedAccounts.tsx:35 FirebaseError: Firebase: Error (auth/multi-factor-auth-required).
    at createErrorInternal (assert.ts:146:1)
    at _fail (assert.ts:65:1)
    at _performSignInRequest (index.ts:252:1)
    at async _logoutIfInvalidated (invalidation.ts:32:1)
    at async _link$1 (link_unlink.ts:66:1)
    at async PopupOperation.onAuthEvent (abstract_popup_redirect_operation.ts:103:1)

Code

import {
  EmailAuthProvider,
  getAuth,
  linkWithPopup,
  OAuthProvider,
  reauthenticateWithCredential,
} from "firebase/auth";

const microsoftProvider = new OAuthProvider("microsoft.com").setCustomParameters({
  prompt: "consent", // Force re-consent.
});
function ConnectedAccounts() {
  async function reauthenticate(password: string) {
    const credential = EmailAuthProvider.credential(getAuth().currentUser!.email!, password);
    await reauthenticateWithCredential(getAuth().currentUser!, credential);
  }

  async function linkWithMicrosoft() {
    try {
      await linkWithPopup(getAuth().currentUser!, microsoftProvider);
    } catch (error: any) {
      if (error?.code === "auth/multi-factor-auth-required") {
        // Fore refresh page when this error gets hit by link being called
        // window.location.reload(); // WORKAROUND
      } else {
        throw error;
      }
    }
  }

  const myPassword = "somePassword";

  async function handleLink() {
    try {
      await reauthenticate(myPassword);
      await linkWithMicrosoft();
    } catch (error: any) {
      if (error?.code === "auth/multi-factor-auth-required") {
        // trigger and complete MFA flow
      } else {
        throw error;
      }
    }
  }

  return <button onClick={handleLink}>Link to Microsoft</button>;
}
  1. Login by email
  2. Call reauthenticate(password)
  3. Complete Password and SMS MFA flow successfully
  4. Popup tab appears
  5. Consent to linking in Microsoft UI
  6. Popup tab auto closes
  7. Second "auth/multi-factor-auth-required" error is thrown in the link functions
  8. At this point the account will show as linked in the Firebase auth UI
  9. Refresh the page and the account will be linked correctly

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions