Verify your Email to continue

When using Web3Auth Email Passwordless via the modal SDK in a React + Vite app, the flow hangs on “Verify your Email to continue” after entering the OTP. OTP is delivered and accepted, but authentication never resolves.

To Reproduce

  1. Open the Web3Auth modal.

  2. Choose Email Passwordless.

  3. Enter email → receive OTP.

  4. Enter OTP → modal shows Verify your Email to continue indefinitely. No success or error returned to the app.

Expected behavior

After a valid OTP, the modal should complete auth and return a connected provider/session (or surface an actionable error).

Screenshots

Additional context

  • Origins are whitelisted in the Web3Auth dashboard.

  • Reproduces in Incognito and after clearing storage/cookies.

  • Using modal flow only (no redirect).

  • Suspect Vite Node polyfills (process.nextTick, stream, events) may be required.

  • Add any other context about the problem here.

Device info

  • Chrome 141.0.7390.123 (Official Build) (arm64)

  • Node v23.7.0

  • MacOS 15.6

  • Model Name: MacBook Air

Hi Bohdan — thanks for the clear reproduction steps and screenshots. I’m sorry you’re hitting this hang — I’ll help get the support team the critical info and suggest a few likely fixes.

Summary of what I pulled from your report

  • Issue type: Reporting an authentication flow issue (Web3Auth modal — Email Passwordless).
  • Platform / environment: React + Vite app, Chrome 141 on macOS 15.6.
  • Node: v23.7.0 (dev server)
  • Flow: Web3Auth modal only (no redirect), OTP delivered and accepted, modal then shows “Verify your Email to continue” indefinitely.
  • Origins: whitelisted in Web3Auth dashboard.
  • You already tried: Incognito and clearing storage/cookies.
  • Suspected cause (your note): missing Vite node polyfills (process.nextTick, stream, events).

Missing/unclear items that will help the support team (please provide if possible)

  1. SDK version(s) you’re using (Web3Auth modal SDK version + any other related libs).
  2. Code initialization & login snippets (the exact Web3Auth modal init/config and how you call the login). Don’t worry about pasting full app code — just the Web3Auth init and the call that opens the modal.
  3. Browser console logs and Network tab (HAR) around the time you enter the OTP. The UI hangs silently so console/network often show the underlying error. You attached images of the modal — thank you — but a console screenshot or exported HAR will be very helpful.

Why this commonly happens (quick diagnosis)

  • Web3Auth code (and many web3 libs) may rely on Node built-ins or globals (process, Buffer, stream, events). Vite’s dev/build can omit those polyfills by default, which can cause libraries to fail silently or hang during promise resolution. Because the OTP is accepted but the auth never resolves, a client-side runtime error or missing polyfill is a likely cause.

Proposed quick troubleshooting steps (most likely to help)

  1. Check browser console and network for errors (CORS, 4xx/5xx, or missing global/builtin errors). If you see errors mentioning “process is not defined”, “stream”, or similar, that points to polyfills.
  2. Add Node polyfills for Vite. Two common approaches:
    • rollup-plugin-polyfill-node (works with Vite build):
      • npm i -D rollup-plugin-polyfill-node
      • in vite.config.ts, add the plugin to build.rollupOptions.plugins. Also ensure global is mapped to globalThis:
        import rollupNodePolyFill from ‘rollup-plugin-polyfill-node’
        export default defineConfig({
        plugins: [react()],
        define: { global: ‘globalThis’ },
        build: { rollupOptions: { plugins: [rollupNodePolyFill()] } }
        })
    • vite-plugin-node-polyfills (alternative): install and add to Vite plugins — this polyfills process, stream, Buffer, events, etc.
  3. Add small runtime defines if you see a specific missing global: in vite.config.ts add define: { global: ‘globalThis’, ‘process.env’: ‘{}’ } or set globalThis.process = { env: {} } early in your app (temporary test) to see if flow completes.
  4. Reproduce with console open — watch for any thrown errors while the modal is stuck. If the client is getting a 200 for OTP confirm but then a JS error prevents resolution, that console stack will point at the missing polyfill or failing module.

If those steps don’t help, please share the (only-if-possible):

  • Web3Auth modal SDK version number.
  • The Web3Auth init snippet and the call that opens the modal (copy-paste).
  • A screenshot of the browser console and the failing network request (or a HAR).

Helpful links

If you want, paste the Web3Auth init/login snippet here and I can point exactly where polyfills or config should be added. I’ll stay on this thread and help triage once you provide the tiny missing bits — thanks for the clear reproduction steps, that helps a lot.

Note: This response was generated by an AI bot and may require further verification. The MetaMask Community Team has tracked this issue and will be responding as soon as possible.

Additionally, we would recommend you to join our biweekly Office Hours to get this resolved even faster. MetaMask Developer Office Hours

here is my vite config

import { exec } from 'node:child_process';
import path from 'node:path';
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
import react from '@vitejs/plugin-react';
import rollupNodePolyFill from 'rollup-plugin-polyfill-node';
import type { Plugin } from 'vite';
import { defineConfig } from 'vite';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import svgr from 'vite-plugin-svgr';

const dts: Plugin = {
name: 'dts-generator',
buildEnd: (error?: Error) => {
if (!error) {
return new Promise((res, rej) => {
exec('tsc --emitDeclarationOnly', (err) => (err ? rej(err) : res()));
});
}
},
};

// Configuring Vite | Vite
export default defineConfig({
define: {
global: 'globalThis',
'process.env': {},
},
esbuild: {
target: 'esnext',
},
build: { rollupOptions: { plugins: [rollupNodePolyFill()] } },
plugins: [
TanStackRouterVite(),
react(),
// svgr options: Options - SVGR
svgr({
// svgrOptions: { icon: true },
include: ['**/*.svg', '**/*.svg?react', '**/*.svg?src'],
}),
dts,
nodePolyfills({
protocolImports: true, // Polyfills for protocol imports like "fs/promises"
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
public: path.resolve(__dirname, './public'),
buffer: 'buffer',
},
},
});

And login page

import { useNavigate } from '@tanstack/react-router';
import { useWeb3Auth, useWeb3AuthConnect } from '@web3auth/modal/react';
import { useEffect, useState } from 'react';
import { MdOutlineAlternateEmail } from 'react-icons/md';
import AddIcon from '@/assets/icon/add.svg?react';
import { ButtonV1 } from '@/shared/ui/buttons';

export const Loading = () => {
const navigate = useNavigate();
const { connect } = useWeb3AuthConnect();
// const { isConnected } = useWeb3Auth();
const [active, setActive] = useState(1);

const handleClick = (value: number, to: string) => {
setActive(value);
setTimeout(() => {
navigate({ to });
}, 100);
};

const handleWeb3Auth = async () => {
try {
console.log('[Web3Auth] Starting connection...');

await connect();
console.log('[Web3Auth] Connection successful');
} catch (error) {
console.error('[Web3Auth] Connection failed:', error);
}
};

// useEffect(() => {
// console.log('[Web3Auth Status]:', isConnected);
// if (isConnected) {
// navigate({ to: '/wallet', replace: true });
// }
// }, [navigate, isConnected]);

return (
<div className="pb-6 h-full flex flex-col justify-between">
<div className="flex flex-col gap-2 px-3 z-10">
<ButtonV1
active={active === 1}
icon={<MdOutlineAlternateEmail size={24} />}
title="Login with Email"
subtitle="Use your email to login"
onClick={handleWeb3Auth}
/>
</div>
</div>
);
};

also my web3auth config

`import { WALLET_CONNECTORS, WEB3AUTH_NETWORK } from ‘
/modal’;
import type { Web3AuthContextConfig } from ‘
/modal/react’;
import { appConfig } from ‘@/appConfig’;

const web3AuthContextConfig: Web3AuthContextConfig = {
web3AuthOptions: {
clientId: appConfig.web3AuthToken,
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
uiConfig: {
loginMethodsOrder: [‘email_passwordless’],
},
modalConfig: {
connectors: {
[WALLET_CONNECTORS.AUTH]: {
label: ‘auth’,
loginMethods: {
email_passwordless: {
name: ‘email passwordless login’,
authConnectionId: ‘dex-email’,
authConnection: ‘email_passwordless’,
},
},
},
},
},
},
};

export default web3AuthContextConfig;`
  <meta charset="UTF-8" />
<meta
  name="viewport"
  content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>`

Hi @Bohdan_Kutsyk have you tried the trouble shooting steps shared earlier

Hi, @shana!

Yes, I have tried all the steps provided before

Hi @Bohdan_Kutsyk apologies for delay waiting on the team’s to review it closely

1 Like

Hi @shana!

Are there any updates?

Not yet waiting for the team let us get back to you tomorrow

1 Like