V11 Identity Token (getAuthTokenInfo) is unverifiable — iss and JWKS don't match the docs

SDK: @web3auth/modal v11.2.0 (Web)
Network: sapphire_mainnet / sapphire_devnet
Doc in question: Embedded Wallets identity token | MetaMask developer documentation

Problem
Backend verification exactly as documented fails for the v11 identity token. Two mismatches:

  1. Issuer: the doc says verify social tokens with issuer: “https://api-auth.web3auth.io”, but the v11 token’s iss is “web3auth io”.
  2. JWKS key not found: the token header kid (e.g. 5Imw8N7KYnswBTF09jMFz8OzgQF8jZSaOQ1buVP5x14) is not served by either documented endpoint:

So jose.jwtVerify(token, createRemoteJWKSet(‘https://api-auth.web3auth.io/jwks’), {…}) throws “no matching key”, regardless of the issuer.

There is also no project verification key shown in our dashboard Project Settings to use as the documented fallback.

Repro

  1. Social login (Google) with @web3auth/modal v11.2.0, sapphire_mainnet.
  2. getAuthTokenInfo() → decode idToken: iss: “web3auth io”, ES256, kid: 5Imw8N7K….
  3. Verify server-side per the id-token doc → fails (issuer mismatch and/or no matching JWKS key).

Expected: the doc’s verification snippet succeeds for v11 tokens.
Actual: it can’t — issuer and signing key don’t match the documented endpoints.

Thanks — I’m on the MetaMask / Web3Auth team. This is an error report (v11 identity token verification failure). I can escalate this to engineering and docs immediately, but I need a couple quick items to triage and speed resolution.

Confirmed from your report:

Please share these (safe to redact any sensitive parts):

  1. The idToken header (base64url-decoded JSON) — shows kid/alg/typ. If OK, paste the full token (we only need header+payload to triage).
  2. The exact server-side verification code you’re using (the jose.jwtVerify call and the remote JWKSet URL).
  3. Your Project ID / Client ID from the dashboard and whether this is mainnet or devnet.

What we’ll do next:

  • I’ll escalate to the Web3Auth engineering/docs team to confirm whether v11 changed issuer/JWKS behavior and arrange a fix or docs update.
  • If this is a regression, we’ll either publish the correct JWKS/issuer or add a project-level verification key in the dashboard and follow up here.

Interim guidance: do not accept or bypass verification for production traffic. If you must test, share the token header and server code and we’ll confirm whether a temporary check is possible.

Once you paste the header + verification snippet and Project ID, I’ll route to the engineers and a human support rep will follow up with fixes or next steps.

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

  1. idToken header + payload (base64url-decoded; PII redacted)

Header:

{ "alg": "ES256", "typ": "JWT", "kid": "5Imw8N7KYnswBTF09jMFz8OzgQF8jZSaOQ1buVP5x14" }

Payload (wallet keys abbreviated and PII redacted; full token available on request):

{
    "iss": "web3auth.io",
    "aud": "BJX893MUtAqIg39P7cCwArFEUELQNQToeYkEW4n9Q7adOS6_X79O6ModGvRCw0Mss-9j40ZgYwUEoRfjlcuCfro",
    "sub": "+Y43E3Nutu0hlrUIOGlqsJplOAc4Jd6NWeWA5UNHzhA=",
    "iat": 1781688399,
    "exp": 1782293199,
    "authConnection": "google",
    "authConnectionId": "web3auth",
    "groupedAuthConnectionId": "web3auth-google-sapphire",
    "wallets": [
      { "type": "web3auth_app_key",      "curve": "ed25519",   "public_key": "fd1e2c…66ff" },
      { "type": "web3auth_threshold_key", "curve": "ed25519",   "public_key": "55094b…3a0f" },
      { "type": "web3auth_app_key",      "curve": "secp256k1", "public_key": "0239d2…335f8" },
      { "type": "web3auth_threshold_key", "curve": "secp256k1", "public_key": "032462…180f5" }
    ],
    "email": "[redacted]",
    "name": "[redacted]"
  }
  1. Server-side verification code (exactly the doc’s approach — it fails)

    import { createRemoteJWKSet, jwtVerify } from 'jose'
    
    const jwks = createRemoteJWKSet(new URL('https://api-auth.web3auth.io/jwks'))
    
    await jwtVerify(idToken, jwks, {
    issuer: ['https://api-auth.web3auth.io', 'https://authjs.web3auth.io'],
    audience: WEB3AUTH_CLIENT_ID,   // the aud above
    clockTolerance: 60,
    })
    // throws JWSNoMatchingKey: the header kid (5Imw8N7K…) isn't in that JWKS,
    // and iss ("web3auth.io") wouldn't match the documented issuer anyway.
    

The documented social JWKS currently serves only these kids (neither is the token’s 5Imw8N7K…):

  1. Project / network
  • Client ID: BJX893MUtAqIg39P7cCwArFEUELQNQToeYkEW4n9Q7adOS6_X79O6ModGvRCw0Mss-9j40ZgYwUEoRfjlcuCfro
  • Network: sapphire_mainnet

No project-level verification key is shown in our dashboard Project Settings, so the doc’s “verification key” fallback isn’t available either.

Hi @sergiu sorry for the wait did anything change since your last message could you share please

Hi. I took a deeper look at the sdk and communication with your backend and I managed to find that the new jwks url is at “https://api.web3auth.io/citadel-service/.well-known/jwks.json”. This one holds the right key for social logins that allows id token signature to be verified.

The docs (and the migration guide to v11) should be updated to reflect this and the fact that the iss is now “web3auth.io”.

I’m unsure if there are more things to update the docs about other than this, I stopped the research after my issue was fixed.

Great thanks for the update the team will have a look. Also you are welcome to open a PR to improve the documentation