SIWW MetaMask login fails with "Invalid request origin" on devnet localhost despite allowlisted domain

Describe your issue or question:

We are experiencing a sudden authentication failure on our development environment. Our MetaMask wallet login flow (SIWW - Sign In With Wallet) was working reliably for 4 months, but starting today (March 26, 2026), all authentication attempts are failing with a 401 “Invalid request origin” error from authjs.web3auth.io/siww/verify. Critically: no code changes were made and production still works fine.

What are you trying to build or integrate?

We’re building a Web3-authenticated SaaS dashboard. Users authenticate by connecting their MetaMask wallet via Web3Auth Modal, which signs a message and returns a JWT for our backend. This flow is working perfectly in production.

What problem or error are you facing?

  • Environment: Devnet, localhost:5173
  • Error: 401 Unauthorized from authjs.web3auth.io/siww/verify
  • Response: {"error":"Invalid request origin","success":false}
  • Status: http://localhost:5173 is already in the Web3Auth dashboard’s “Allowlist for Devnet Environment”
  • Production Status: Identical code and same devnet client_id work fine in production (https:// domain)
  • Regression: This worked every day for 4 months until the past few days

Tech stack:

  • Frontend: React 19.1.1 with TypeScript
  • Web3Auth SDK: @web3auth/modal v10.15.0
  • Wallet: MetaMask
  • Network: Sapphire Devnet
  • Build tool: Vite

Code snippets:

Web3Auth initialization (unchanged):

export const web3AuthOptions: Web3AuthContextConfig['web3AuthOptions'] = {
  clientId: import.meta.env.VITE_WEB3AUTH_DEVNET_CLIENT_ID,
  web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
  uiConfig: {
    widgetType: 'modal',
  },
};

Login handler (unchanged):

const { connect } = useWeb3AuthConnect();
await connect(); // This is where the SIWW call fails

Exact error and logs:

Request to https://authjs.web3auth.io/siww/verify:

POST /siww/verify HTTP/1.1
Host: authjs.web3auth.io
Origin: http://localhost:5173
Client_id: BMOk8NhyqVmonRctwP2B11x4i6spqAu75HsXa9QQK2A5bwQyPQ9mDpthOoiLAMqdi7I1BjrZuuwr2akUf3z7tq0
Web3auth_network: sapphire_devnet
Wallet_provider: metamask

Response: 401 Unauthorized
{"error":"Invalid request origin","success":false}

Package details:

  • `@web3auth/modal ^10.14.1 (installed 10.15.0)
  • @web3auth/auth: v10.8.0 (dependency)
  • Project: Kryptac (sapphire_devnet)

Additional context:

  1. We verified the allowlist configuration multiple times and even removed/re-added http://localhost:5173
  2. The production deployment uses the same client_id and same code but with an http:// domain — it works without issues
  3. This suggests the devnet SIWW service may have either:
  • Enforced HTTPS-only origins (which would break HTTP localhost)
  • Had a configuration change or incident in the past couple of days
  • Changed how it validates the Origin header

Could you advise if there was a recent change to the SIWW endpoint or devnet infrastructure today?

This is an error report (SIWW auth failure). Thanks — you provided good detail (SDK: MetaMask Embedded/Web3Auth, @web3auth/modal v10.15.0, React/Vite, devnet localhost:5173, request/response). Quick summary of what I checked and next steps:

Findings

  • Docs/KB: Sapphire Devnet should allow localhost origins; Sapphire Mainnet does not. I didn’t find any KB entry or known incident saying authjs.web3auth.io changed origin rules recently.

Immediate checks (do these in order)

  1. Confirm the clientId is tied to a Sapphire Devnet project in the Web3Auth dashboard (clientId vs environment mismatch is a common cause). Ensure web3AuthNetwork === “sapphire_devnet” in runtime.
  2. Exact allowlist match: make sure the dashboard entry exactly matches the Origin header (no trailing slash). Add these variants too: http://localhost:5173 and http://127.0.0.1:5173.
  3. Recreate the allowlist entry (delete then add) or create a fresh devnet project/client_id and allowlist the origin to rule out a dashboard config corruption.
  4. Verify the env var at runtime (VITE_WEB3AUTH_DEVNET_CLIENT_ID) — confirm the built app is actually using the expected dev clientId (build-time env mismatch can happen with Vite).
  5. Test with a direct request to verify endpoint to isolate browser vs server: curl -v -X POST https://authjs.web3auth.io/siww/verify -H “Origin: http://localhost:5173” -d ‘{}’
  6. Check anything that might rewrite Origin (reverse proxy, dev tooling, or browser extension). Try an incognito profile with no extensions.
  7. Quick workaround: try serving your dev site over HTTPS (localhost with a local cert / mkcert) or use 127.0.0.1 to see if behaviour changes.

If those don’t fix it

  • If a new client_id + allowlist works, the original client config may have been changed/rotated on the server side — please create a new devnet client and test.
  • If a new client_id also fails, please tell me if you want me to escalate to the MetaMask Embedded/Web3Auth team. For escalation I’ll need your devnet client_id, the first failing timestamp (UTC), and one full failing request/response (you already included a good example).

What I can confirm from KB: devnet should allow localhost; no KB evidence of a recent origin-policy change. If you want, I can open an internal ticket for you — say the word and share the client_id + timestamp and I’ll escalate to the team for immediate investigation.

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 monthly Office Hours to discuss this with our engineering team. MetaMask Developer Office Hours

We’ve isolated the root cause of the 401 “Invalid request origin” error on devnet. The @web3auth/modal SDK constructs the SIWW payload with "audience" set to window.location.hostname (e.g., "localhost"), but the authjs.web3auth.io/siww/verify endpoint validates this field against the full allowlisted origin URL (e.g., http://localhost:5173). When the origin uses a non-standard port, the hostname alone doesn’t match the allowlist entry, and the server rejects the request.

This is not a configuration issue. Our allowlist is correct, CORS passes, and the same client_id works on our production deployment (http://34.1.44.49, default port 80). The bug specifically affects origins with non-standard ports (e.g., :5173) because the SDK strips the protocol and port from the audience.


Proof (reproducible via curl)

Using our devnet client_id BMOk8NhyqVmonRctwP2B11x4i6spqAu75HsXa9QQK2A5bwQyPQ9mDpthOoiLAMqdi7I1BjrZuuwr2akUf3z7tq0:

Test 1 — What the SDK sends (bare hostname) → 401 FAILS:

curl -s -X POST "https://authjs.web3auth.io/siww/verify" \
  -H "Content-Type: application/json; charset=utf-8" \
  -H "Origin: http://localhost:5173" \
  -H "client_id: BMOk8NhyqVmonRctwP2B11x4i6spqAu75HsXa9QQK2A5bwQyPQ9mDpthOoiLAMqdi7I1BjrZuuwr2akUf3z7tq0" \
  -H "wallet_provider: metamask" \
  -H "web3auth_network: sapphire_devnet" \
  --data-raw '{"signature":{"s":"0xfake","t":"eip191"},"message":"test","issuer":"metamask","audience":"localhost","timeout":604800}'

Response: 401 — {"error":"Invalid request origin","success":false}

Test 2 — Full origin with port → origin check PASSES:

curl -s -X POST "https://authjs.web3auth.io/siww/verify" \
  -H "Content-Type: application/json; charset=utf-8" \
  -H "Origin: http://localhost:5173" \
  -H "client_id: BMOk8NhyqVmonRctwP2B11x4i6spqAu75HsXa9QQK2A5bwQyPQ9mDpthOoiLAMqdi7I1BjrZuuwr2akUf3z7tq0" \
  -H "wallet_provider: metamask" \
  -H "web3auth_network: sapphire_devnet" \
  --data-raw '{"signature":{"s":"0xfake","t":"eip191"},"message":"test","issuer":"metamask","audience":"http://localhost:5173","timeout":604800}'

Response: 500 — {"error":"Message did not match the regular expression.","success":false} (500 is expected — fake signature. The important thing is it’s not a 401, meaning the origin check passed.)

The only difference between these two requests is the audience value in the JSON body. This proves the server validates audience against the allowlist.


Why production works but localhost doesn’t

Our production deployment at http://34.1.44.49 (default port 80) works because:

  • SDK sends "audience": "34.1.44.49" (hostname)
  • Allowlist has http://34.1.44.49
  • No port mismatch — port 80 is implicit in both

Our dev environment at http://localhost:5173 (non-standard port) fails because:

  • SDK sends "audience": "localhost" (hostname only, port stripped)
  • Allowlist has http://localhost:5173
  • "localhost""localhost:5173" — the non-standard port is lost

The SDK appears to use window.location.hostname instead of window.location.origin (or at minimum window.location.host which includes the port) when constructing the audience field.


What the SDK sends (captured from browser DevTools → Copy as cURL):

{
  "signature": { "s": "0x8d0e0c...", "t": "eip191" },
  "message": "http://localhost:5173 wants you to sign in with your Ethereum account:\n0xeb0eb3bcf131ac2ea1afabfffbcee9056a4886cb\n\n\nURI: http://localhost:5173/\nVersion: 1\nChain ID: 11155111\nNonce: b3eaf74a...\nIssued At: 2026-03-28T14:06:46.927Z",
  "issuer": "metamask",
  "audience": "localhost",
  "timeout": 604800
}

Note: the message field correctly uses the full origin (http://localhost:5173), but the audience field only has the bare hostname (localhost). These are inconsistent.


Environment:

  • SDK: @web3auth/modal ^10.14.1 (installed: 10.15.0)
  • @web3auth/auth: 10.8.0 (dependency of modal)
  • Network: Sapphire Devnet
  • Client ID: BMOk8NhyqVmonRctwP2B11x4i6spqAu75HsXa9QQK2A5bwQyPQ9mDpthOoiLAMqdi7I1BjrZuuwr2akUf3z7tq0
  • Wallet: MetaMask (browser extension)
  • Frontend: React 19.1.1, Vite (dev server on port 5173), TypeScript
  • OS: Windows 11, Chrome 146
  • Production URL: http://34.1.44.49 (works)
  • Dev URL: http://localhost:5173 (fails)

Timeline:

  • Working: ~4 months (Nov 2025 – Mar 25, 2026)
  • Broken: Starting March 26, 2026
  • No code changes to the authentication flow

Allowlisted origins (all present in dashboard):

  • http://localhost:5173
  • http://127.0.0.1:5173
  • http://192.168.0.104:5173
  • localhost:5173
  • http://localhost

Request: Could you confirm whether there was a server-side change to how authjs.web3auth.io/siww/verify validates the audience field, or an SDK change in how audience is constructed? The fix would be either:

  1. SDK sends window.location.host or window.location.origin instead of window.location.hostname
  2. Server-side matching accounts for non-standard ports when comparing audience to the allowlist

Hey @angeloaad

Thanks for raising this. I’m unable to reproduce the issue, however I’ll ask the engineering team once to take a look as well.

1 Like

Thank you Yashovardhan it means a lot!, I am waiting for a response from the team!

Hello @yashovardhan how are you?

Any updates on the issue?

Hi team,

Following up on this issue — the login flow is now working again as of today (April 4,
2026) on both http://localhost and http://localhost:5173, without any changes on our end.

To summarize the timeline:

  • Working: ~4 months continuously (Nov 2025 – Mar 25, 2026)
  • Broken: March 26 – April 3, 2026 (9 days)
  • Working again: April 4, 2026 — no code or config changes on our side

Since we made zero changes to resolve this, something changed on your infrastructure
between March 26 and April 4. We’d like to understand what happened for a few reasons:

  1. It could break again. If we don’t know the root cause, we have no way to anticipate or
    protect against a recurrence.
  2. We lost 9 days of development time debugging an issue that was outside our control.
  3. The root cause we identified still stands — the SDK sends audience: “localhost” (bare
    hostname) in the SIWW payload. If your server started accepting this again, we’d like to
    know if that was intentional or a side effect of something else.

Specific questions:

  • Was there an incident or maintenance on the authjs.web3auth.io/siww/verify endpoint
    between March 26–April 4?
  • Was a fix or rollback deployed? If so, what was changed?
  • Is there a status page or incident log we should be monitoring so we’re not blindsided if
    this happens again?
  • Is the devnet SIWW service considered stable for ongoing development, or should we expect
    periodic disruptions?

We’d appreciate a clear answer so we can make an informed decision about whether to
continue relying on devnet or migrate to mainnet.