-
Notifications
You must be signed in to change notification settings - Fork 18
Description
📝 Description
Description
The zksync-sso library does not support passing the userVerification parameter during passkey authentication, which causes authentication failures when using certain passkey providers (notably Google Password Manager) that require explicit user verification.
Problem
When calling passkeyClient.createSession(), users are prompted to select a passkey. However, after selecting the passkey, the transaction fails with the following error:
Payment error: TransactionExecutionError: An error occurred.
Request Arguments:
from: 0xe9846123569718E020bF55C92CC53fBa414A6719
to: 0x64Fa4b6fCF655024e6d540E0dFcA4142107D4fBC
data: 0x5a0694d2...
gas: 10000000
Details: User verification required, but user could not be verified
Version: viem@2.35.1
Root Cause Confirmed: The issue is caused by the missing userVerification property in the WebAuthn authentication request. Google Password Manager (and potentially other passkey providers) do not prompt for biometric/PIN verification unless the userVerification property is explicitly set to "required" in the WebAuthn request options.
Code Context
PasskeyClient Creation
const passkeyClient = createZksyncPasskeyClient({
address: getAddress(active?.address ?? ''),
credentialPublicKey: Uint8Array.from(
Buffer.from(credentialPublicKeys[active?.address ?? ''], 'base64')
),
userDisplayName: response.data.email,
userName: response.data.email,
contracts: {
session: '0x64Fa4b6fCF655024e6d540E0dFcA4142107D4fBC',
recovery: '0x6AA83E35439D71F28273Df396BC7768dbaA9849D',
passkey: '0x006ecc2D79242F1986b7cb5F636d6E3f499f1026',
accountFactory: '0x7230ae6D4a2C367ff8493a76c15F8832c62f9fE9',
recoveryOidc: '0x116A07f88d03bD3982eBD5f2667EB08965aAe98c',
oidcKeyRegistry: '0x0EEeA31EA37959316dc6b50307BaF09528d3fcc4'
} as any,
chain: zksyncSepoliaTestnet as any,
transport: http() as any
});Session Configuration and Creation
const sessionConfig = {
signer: sessionPublicKey,
expiresAt: BigInt(Math.floor(Date.now() / 1000) + 60 * 5), // 5 minutes
feeLimit: {
limitType: LimitType.Lifetime as any,
limit: parseEther('0.1'),
period: 0n
},
transferPolicies: transferPolicies as any,
callPolicies: []
};
await passkeyClient.createSession({
sessionConfig
});Where transferPolicies contains payment targets and limits:
const transferPolicies = shopPayments.map((payment) => ({
target: getAddress(payment.walletAddress),
maxValuePerUse: parseEther(payment.amount.eth.toString()),
valueLimit: {
limitType: LimitType.Lifetime as any,
limit: parseEther(payment.amount.eth.toString()),
period: 0n
}
}));Current Implementation
The requestPasskeyAuthentication function in zksync-sso/client/passkey/actions/passkey.ts does not accept or pass the userVerification parameter:
export const requestPasskeyAuthentication = async (
args: RequestPasskeyAuthenticationArgs
): Promise<RequestPasskeyAuthenticationReturnType> => {
const passkeyAuthenticationOptions = await generatePasskeyAuthenticationOptions({
challenge: toBytes(args.challenge),
});
// ... rest of the function
};Proposed Solution
Add an optional userVerification parameter to createZksyncPasskeyClient that gets passed through to all authentication requests:
- Update
createZksyncPasskeyClientparameters:
const passkeyClient = createZksyncPasskeyClient({
address: getAddress(active?.address ?? ''),
credentialPublicKey: Uint8Array.from(...),
userDisplayName: response.data.email,
userName: response.data.email,
userVerification: "required", // <-- Add this option
contracts: { ... },
chain: zksyncSepoliaTestnet as any,
transport: http() as any
});- Update
requestPasskeyAuthenticationto accept and use the parameter:
export const requestPasskeyAuthentication = async (
args: RequestPasskeyAuthenticationArgs
): Promise<RequestPasskeyAuthenticationReturnType> => {
const passkeyAuthenticationOptions = await generatePasskeyAuthenticationOptions({
challenge: toBytes(args.challenge),
userVerification: args.userVerification || "preferred",
});
// ... rest of the function
};This way, the userVerification setting is configured once at the client level and automatically applied to all authentication requests.
Steps to Reproduce
- Create a passkey client using
createZksyncPasskeyClientwith the configuration shown above - Create a session configuration with transfer policies and fee limits
- Call
passkeyClient.createSession({ sessionConfig }) - Select a passkey from Google Password Manager when prompted
- Observe the error: "User verification required, but user could not be verified"
Expected Behavior
The passkey authentication should prompt for biometric/PIN verification and successfully complete the session creation, allowing the subsequent transactions to execute.
Actual Behavior
The authentication fails without prompting for user verification, causing the transaction to fail with a TransactionExecutionError.
Environment
- Library:
zksync-sso@0.3.3 - viem Version:
2.35.1 - Chain: zkSync Sepolia Testnet
- Passkey Provider: Google Password Manager
- Browser: Chrome/Edge (Chromium-based)
- Related Libraries:
@simplewebauthn/browser@13.1.0,@simplewebauthn/server@13.1.1
Additional Context
This is a WebAuthn specification requirement. According to the WebAuthn spec, the userVerification parameter controls whether the authenticator should verify the user (via PIN, biometric, etc.). Many passkey providers, especially Google Password Manager, rely on this flag being explicitly set to trigger the verification flow.
The issue occurs specifically during the createSession call, which internally uses requestPasskeyAuthentication to sign the session creation transaction. Without the userVerification flag, the passkey is selected but the verification step is skipped, leading to the error.
🤔 Rationale
No response
📋 Additional context
No response