v9 headless login (connectTo with idToken) hardcodes the user_id JWT claim — breaks non-Firebase custom connections, verifierIdField is ignored
SDK version
@web3auth/react-native-sdk@9.0.0 (latest at time of writing)
Summary
When connectTo() is called with a top-level idToken (the headless / SFA-style login path), the SDK derives the verifier ID from the JWT with a hardcoded claim lookup:
// dist/lib.esm/Web3Auth.js
getUserIdFromJWT(token) {
const decoded = jwtDecode(token);
return decoded["user_id"];
}
user_id is a Firebase-specific claim. Standard OIDC idTokens — Google Sign-In (native @react-native-google-signin), Sign in with Apple, Auth0, Cognito, and most other BYO-JWT providers — carry the user identifier in sub and have no user_id claim. For those tokens getUserIdFromJWT returns undefined, which is then used as verifierId in getNodeDetails() / retrieveShares(), and the login fails.
extraLoginOptions.verifierIdField (shown in the migration docs' custom-JWT examples) does not help: it is never consulted on this code path. grep -rn 'verifierIdField' node_modules/@web3auth/react-native-sdk/dist/ returns zero matches — extraLoginOptions is only forwarded to the hosted auth page in the browser-redirect flows.
The docs' Firebase example works only coincidentally: Firebase idTokens contain both sub and user_id (with identical values), which masks the hardcoded lookup.
How we call it today (working, @web3auth/single-factor-auth@9.5.0)
We use native Google Sign-In / Sign in with Apple to obtain the idToken on-device, then log in headlessly through a grouped (aggregate) verifier. SFA accepts the verifier ID explicitly, so any JWT shape works:
// idToken from native sign-in:
// Google: GoogleSignin.signIn() -> data.idToken, verifierId = data.user.id (= token's `sub`)
// Apple: identityToken, verifierId = decoded.sub / credential.user
await web3authSFAuth.connect({
verifier: aggregateVerifier, // grouped/aggregate verifier
verifierId, // passed explicitly — the token's `sub`
idToken,
subVerifierInfoArray: [
{
verifier: subVerifier, // per-provider sub-verifier
idToken,
},
],
});
What the v9 migration should look like (fails)
Per the migration guide, the equivalent grouped-connection call on v9 is:
await connectTo({
authConnection: AUTH_CONNECTION.CUSTOM,
authConnectionId: subVerifier, // was subVerifierInfoArray[0].verifier
groupedAuthConnectionId: aggregateVerifier, // was `verifier`
idToken, // top-level, headless path
// no way to pass verifierId explicitly, and
// extraLoginOptions: { verifierIdField: 'sub' } is ignored on this path
});
Internally this reproduces the same aggregate flow as SFA (connectTo builds the subVerifierInfoArray itself), except the verifier ID: instead of the explicit verifierId SFA accepted, it calls getUserIdFromJWT(idToken) — which returns undefined for Google/Apple tokens, so verifierParams.verifier_id is undefined and share retrieval fails.
Steps to reproduce
- Configure a custom grouped verifier whose JWT verifier ID field is
sub — e.g. a Google connection used with the native Google Sign-In SDK.
- Obtain a Google OIDC idToken on-device. Decode it: it has
sub, no user_id.
- Call
connectTo as shown above.
getUserIdFromJWT(idToken) returns undefined → verifierParams.verifier_id is undefined → share retrieval fails.
Expected behavior
One (ideally both) of:
connectTo honors extraLoginOptions.verifierIdField (matching the documented API surface) when deriving the verifier ID in the headless path, e.g. decoded[verifierIdField ?? 'user_id'].
- A sane default fallback:
decoded.user_id ?? decoded.sub — sub is the standard OIDC subject claim, and Firebase tokens keep working since they carry both claims with equal values.
Why this matters for migrations
This blocks the documented migration path from @web3auth/single-factor-auth, which accepted an explicit verifierId and therefore worked with any JWT shape. Apps using native Google/Apple sign-in (i.e. anything that isn't Firebase) currently cannot move their SFA flows to v9 connectTo without monkey-patching Web3Auth.prototype.getUserIdFromJWT. Because the wallet key is derived from (verifier, verifierId), the fix must produce the same sub value users logged in with via SFA — otherwise existing users lose access to their wallets.
Environment
- React Native (Expo),
@web3auth/react-native-sdk@9.0.0
- Bundled
@web3auth/auth@10.8.0
- Grouped custom connection (aggregate verifier + sub-verifiers), tokens from native Google Sign-In and Sign in with Apple
v9 headless login (
connectTowithidToken) hardcodes theuser_idJWT claim — breaks non-Firebase custom connections,verifierIdFieldis ignoredSDK version
@web3auth/react-native-sdk@9.0.0(latest at time of writing)Summary
When
connectTo()is called with a top-levelidToken(the headless / SFA-style login path), the SDK derives the verifier ID from the JWT with a hardcoded claim lookup:user_idis a Firebase-specific claim. Standard OIDC idTokens — Google Sign-In (native@react-native-google-signin), Sign in with Apple, Auth0, Cognito, and most other BYO-JWT providers — carry the user identifier insuband have nouser_idclaim. For those tokensgetUserIdFromJWTreturnsundefined, which is then used asverifierIdingetNodeDetails()/retrieveShares(), and the login fails.extraLoginOptions.verifierIdField(shown in the migration docs' custom-JWT examples) does not help: it is never consulted on this code path.grep -rn 'verifierIdField' node_modules/@web3auth/react-native-sdk/dist/returns zero matches —extraLoginOptionsis only forwarded to the hosted auth page in the browser-redirect flows.The docs' Firebase example works only coincidentally: Firebase idTokens contain both
subanduser_id(with identical values), which masks the hardcoded lookup.How we call it today (working,
@web3auth/single-factor-auth@9.5.0)We use native Google Sign-In / Sign in with Apple to obtain the idToken on-device, then log in headlessly through a grouped (aggregate) verifier. SFA accepts the verifier ID explicitly, so any JWT shape works:
What the v9 migration should look like (fails)
Per the migration guide, the equivalent grouped-connection call on v9 is:
Internally this reproduces the same aggregate flow as SFA (
connectTobuilds thesubVerifierInfoArrayitself), except the verifier ID: instead of the explicitverifierIdSFA accepted, it callsgetUserIdFromJWT(idToken)— which returnsundefinedfor Google/Apple tokens, soverifierParams.verifier_idisundefinedand share retrieval fails.Steps to reproduce
sub— e.g. a Google connection used with the native Google Sign-In SDK.sub, nouser_id.connectToas shown above.getUserIdFromJWT(idToken)returnsundefined→verifierParams.verifier_idisundefined→ share retrieval fails.Expected behavior
One (ideally both) of:
connectTohonorsextraLoginOptions.verifierIdField(matching the documented API surface) when deriving the verifier ID in the headless path, e.g.decoded[verifierIdField ?? 'user_id'].decoded.user_id ?? decoded.sub—subis the standard OIDC subject claim, and Firebase tokens keep working since they carry both claims with equal values.Why this matters for migrations
This blocks the documented migration path from
@web3auth/single-factor-auth, which accepted an explicitverifierIdand therefore worked with any JWT shape. Apps using native Google/Apple sign-in (i.e. anything that isn't Firebase) currently cannot move their SFA flows to v9connectTowithout monkey-patchingWeb3Auth.prototype.getUserIdFromJWT. Because the wallet key is derived from(verifier, verifierId), the fix must produce the samesubvalue users logged in with via SFA — otherwise existing users lose access to their wallets.Environment
@web3auth/react-native-sdk@9.0.0@web3auth/auth@10.8.0