How to do MFA authentication

Hi, I am an engineer from Japan.
I am writing this question using a translation tool.

Thanks for developing a great tool!
I have a question about MFA authentication using no-modal-SDK.
I have set mfaLevel: “mandatory”, but when I set up the MFA element for the first time, the deviceShareFactor and other settings are skipped after the second time. Am I wrong in setting it to “mandatory” to ensure that the MFA element is always set up? Why is it skipped? Also, how do I check the MFA elements I have set?

I would like to ask for your help!
Thank you in advance!

"use client";

import {
  CHAIN_NAMESPACES,
  IProvider,
  WALLET_ADAPTERS,
  WEB3AUTH_NETWORK,
} from "@web3auth/base";
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-provider";
import { Web3AuthNoModal } from "@web3auth/no-modal";
import {
  OpenloginAdapter,
  OpenloginUserInfo,
  WhiteLabelData,
} from "@web3auth/openlogin-adapter";
import { useEffect, useState } from "react";
import Web3 from "web3";

const chainConfig = {
  chainNamespace: CHAIN_NAMESPACES.EIP155,
  chainId: "0x61", // BNB Smart Chain Testnet
  rpcTarget: "https://data-seed-prebsc-1-s1.binance.org:8545/",
  displayName: "BNB Smart Chain Testnet",
  blockExplorer: "https://testnet.bscscan.com/",
  ticker: "BSC",
  tickerName: "BNB",
};

const uiConfig: WhiteLabelData = {
  appName: "My App",
  mode: "dark",
  theme: {
    primary: "#FF0000",
  },
};

const privateKeyProvider = new EthereumPrivateKeyProvider({
  config: {
    chainConfig,
  },
});

const web3auth = new Web3AuthNoModal({
  clientId:
    "****",
  chainConfig,
  web3AuthNetwork: "sapphire_devnet",
  privateKeyProvider,
  uiConfig,
});

const openloginAdapter = new OpenloginAdapter({
  loginSettings: {
    mfaLevel: "mandatory",
  },
  adapterSettings: {
    uxMode: "popup",
    loginConfig: {
      jwt: {
        verifier: "*****",
        typeOfLogin: "jwt",
        clientId:
          "*****",
      },
    },
    mfaSettings: {
      deviceShareFactor: {
        enable: true,
        priority: 1,
        mandatory: true,
      },
      backUpShareFactor: {
        enable: true,
        priority: 2,
        mandatory: true,
      },
      socialBackupFactor: {
        enable: true,
        priority: 3,
        mandatory: true,
      },
      passwordFactor: {
        enable: true,
        priority: 4,
        mandatory: true,
      },
    },
  },
});
web3auth.configureAdapter(openloginAdapter);

const Page = () => {
  const [provider, setProvider] = useState<IProvider | null>(null);
  const [loggedIn, setLoggedIn] = useState(false);
  const [user, setUser] = useState<Partial<OpenloginUserInfo>>();
  const [address, setAddress] = useState<string[]>([]);

  useEffect(() => {
    const init = async () => {
      try {
        await web3auth.init();
        setProvider(web3auth.provider);

        if (web3auth.connected) {
          setLoggedIn(true);
        }
      } catch (error) {
        console.error(error);
      }
    };

    init();
  }, []);

  const login = async () => {
    const web3authProvider = await web3auth.connectTo(
      WALLET_ADAPTERS.OPENLOGIN,
      {
        loginProvider: "jwt",
        extraLoginOptions: {
          verifier: "*****",
          id_token:
            "*********",
          verifierIdField: "sub",
        },
      }
    );
    setProvider(web3authProvider);
    if (web3auth.connected) {
      setLoggedIn(true);
    }
  };

  const logout = async () => {
    await web3auth.logout();
    setProvider(null);
    setLoggedIn(false);
    uiConsole("logged out");
  };

  const getUserInfo = async () => {
    const user = await web3auth.getUserInfo();
    uiConsole(user);
    setUser(user);
  };

  const getAccounts = async () => {
    if (!provider) {
      uiConsole("provider not initialized yet");
      return;
    }
    const web3 = new Web3(provider as any);

    const address = await web3.eth.getAccounts();
    uiConsole(address);
    setAddress(address);
  };

  const getBalance = async () => {
    if (!provider) {
      uiConsole("provider not initialized yet");
      return;
    }
    const web3 = new Web3(provider as any);

    const address = (await web3.eth.getAccounts())[0];

    const balance = web3.utils.fromWei(
      await web3.eth.getBalance(address), // Balance is in wei
      "ether"
    );
    uiConsole(balance);
  };

  const signMessage = async () => {
    if (!provider) {
      uiConsole("provider not initialized yet");
      return;
    }
    const web3 = new Web3(provider as any);

    const fromAddress = (await web3.eth.getAccounts())[0];

    const originalMessage = "YOUR_MESSAGE";

    const signedMessage = await web3.eth.personal.sign(
      originalMessage,
      fromAddress,
      "test password!" // configure your own password here.
    );
    uiConsole(signedMessage);
  };

  const uiConsole = (...args: any[]): void => {
    const el = document.querySelector("#console>p");
    if (el) {
      el.innerHTML = JSON.stringify(args || {}, null, 2);
      console.log(...args);
    }
  };

  const unloggedInView = (
    <button onClick={login} className="card">
      Login
    </button>
  );

  const loggedInView = (
    <>
      <div className="flex-container">
        <div>
          <button onClick={getUserInfo} className="card">
            Get User Info
          </button>
          <div>{JSON.stringify(user)}</div>
        </div>
        <div>
          <button onClick={getAccounts} className="card">
            Get Accounts
          </button>
          <div>{JSON.stringify(address)}</div>
        </div>
        <div>
          <button onClick={getBalance} className="card">
            Get Balance
          </button>
        </div>
        <div>
          <button onClick={signMessage} className="card">
            Sign Message
          </button>
        </div>
        <div>
          <button onClick={logout} className="card">
            Log Out
          </button>
        </div>
      </div>
    </>
  );

  return (
    <>
      <div>{loggedIn ? loggedInView : unloggedInView}</div>
    </>
  );
};

export default Page;

Hi @tanaka,

i hope you are doing good.

I would like to inform you that, as de documentations says, mfasettings are only available since the Scale Plan.

To verify your MFA account elements, you can go to : https://app.openlogin.com/

please contact me if you any further assistance.

Hi @TomTom
Thank you for your response!

If it is a development environment, isn’t mfasettings free?
I am trying to do a technical validation of dapp.

You can use this feature in the development environment for free.

Hi @tanaka

I’m trying mfalevel in sapphire_devnet as mandatory and it works ok.

Can you share the packacke.json to know the version you are using?

Thanks

@TomTom

Hi
Is package.json all you need?

{
  "name": "nextjs-no-modal-quick-start",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "//IMP START": "IMP START - Web3Auth Installation",
  "dependencies": {
    "@web3auth/base": "^8.0.0",
    "@web3auth/ethereum-provider": "^8.0.1",
    "@web3auth/no-modal": "^8.0.1",
    "@web3auth/openlogin-adapter": "^8.0.1",
    "bufferutil": "^4.0.8",
    "next": "13.4.9",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "utf-8-validate": "^6.0.3",
    "web3": "^4.1.1"
  },
  "//IMP END": "IMP END - Web3Auth Installation",
  "devDependencies": {
    "@types/elliptic": "^6.4.14",
    "@types/node": "20.4.1",
    "@types/react": "18.2.14",
    "@types/react-dom": "18.2.7",
    "eslint": "8.44.0",
    "eslint-config-next": "13.4.9",
    "typescript": "5.1.6"
  }
}

Thanks

Your code seems to be ok and you are using the last web3auth version, Do you have an online site to view it working ?

Thank you for confirming the code.

We are currently checking with the local host,
We have not deployed it online.

Video captured.
Does this help in determining the cause?

  • First time it asks for MFA authentication setup with mfaLevel: "mandatory".
  • After the second time, the setting of the MFA authentication factor is skipped

1st try

Hey @tanaka,

First off, I really appreciate you sending over those videos. It helped a lot in understanding the situation. From what I’ve seen, the functionality of mfaLevelmandatory is indeed working correctly.

Initially, it mandates setting up MFA (Multi-Factor Authentication), which is unavoidable. Then, upon logging in via your social login (1/n) for the first time and continuing from the same browser(2/n), we’re able to reconstruct your private key successfully with 2 factors.

If you’re keen to further explore this functionality, you might want to try logging in with the same social login but from a different computer, or by clearing your browser’s cache. This should prompt the system to ask for an additional factor, allowing the private key to be reconstructed under new conditions.

I hope this explanation makes things clearer. If you have any more questions or need further clarification, please don’t hesitate to reach out to me. I’m here to help!

Thanks @TomTom !

I had already confirmed the behavior of a different browser or cache deletion using the same social login information.

My understanding was that with mandatory, it was possible to control the same browser to ask for the MFA element setting each time, but I guess not.

For example, if I want to “not use password setting as MFA factor”, should I set passwordFactor to enable: false?

Also, regarding passwords or secret questions and answers, these do not appear to be stored on the user’s device. Where is it stored?

Thank you for taking the time to address my questions!
It has been really helpful.

const openloginAdapter = new OpenloginAdapter({
  loginSettings: {
    mfaLevel: "mandatory",
  },
  adapterSettings: {
    uxMode: "popup",
    loginConfig: {
      jwt: {
        verifier: "web3auth_verifier",
        typeOfLogin: "jwt",
        clientId: "web3auth_client_id",
      },
    },
    mfaSettings: {
      deviceShareFactor: {
        enable: true,
        priority: 1,
        mandatory: true,
      },
      backUpShareFactor: {
        enable: true,
        priority: 2,
        mandatory: true,
      },
      socialBackupFactor: {
        enable: true,
        priority: 3,
        mandatory: true,
      },
      passwordFactor: {
        enable: true,
        priority: 4,
        mandatory: true,
      },
    },
  },
});

hi @tanaka

Please contact me if you have any other question

It’s also advised to use a strong password to ensure that if someone with malicious intent obtains one of your credentials, they won’t be able to easily brute force their way through your password.

Hi, @TomTom

I understand that passwords (and perhaps even passphrases) are stored encrypted on the web3auth metadata server!

Thanks also for the advice about password strength.

You have really helped me! Thank you very much.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.