Passkey Signup

It is possible to create wallets for users using just a passkey. This is useful for creating wallets for users if you don’t want to go through the email OTP or magic link flow.

If you create a passkey without an email associated with the user, you risk your users losing access to their wallets if they lose their device.

Recommended security practice: Proxy authentication requests to your backend server to enforce additional security measures:

  • When a user attempts to sign up with both passkey and email, you can first require email verification before allowing the passkey to be created
  • Alternatively, you can restrict initial signup to email-based methods only (which inherently verify email ownership), then allow users to add passkeys after their account is established
  • This approach gives you greater control over the authentication flow and helps prevent account recovery issues

By implementing server-side verification, you ensure that passkeys are only created for verified identities, reducing the risk of permanent access loss.

Authenticate a user with email and passkey

If you want to allow sign-up and login with a passkey, you can ask the user for an email to associate with their passkey. This way, they can log in with their email or passkey in the future. Under the hood, the email is also used to check if an account exists already so you can have a unified sign-up and login flow.

It’s important that you validate this email before creating an account for the user. This is to prevent users from losing access to their wallets if they lose their device.

import { 
import signer
signer
} from "./signer";
const
const result: any
result
= await
import signer
signer
.
any
authenticate
({
type: string
type
: "passkey",
email: string
email
: "[email protected]",
});
import { 
class AlchemyWebSigner

A SmartAccountSigner that can be used with any SmartContractAccount

AlchemyWebSigner
} from "@account-kit/signer";
export const
const signer: AlchemyWebSigner
signer
= new
new AlchemyWebSigner(params: AlchemySignerParams): AlchemyWebSigner

Initializes an instance with the provided Alchemy signer parameters after parsing them with a schema.

AlchemyWebSigner
({
client: ({ connection: { apiKey: string; rpcUrl?: undefined; jwt?: undefined; } | { jwt: string; rpcUrl?: undefined; apiKey?: undefined; } | { rpcUrl: string; apiKey?: undefined; jwt?: undefined; } | { rpcUrl: string; jwt: string; apiKey?: undefined; }; ... 4 more ...; enablePopupOauth?: boolean | undefined; } | AlchemySignerWebClient) & (AlchemySignerWebClient | ... 1 more ... | undefined)
client
: {
connection: { apiKey: string; }
connection
: {
apiKey: string
apiKey
: "API_KEY",
},
iframeConfig: { iframeContainerId: string; }
iframeConfig
: {
iframeContainerId: string
iframeContainerId
: "alchemy-signer-iframe-container",
}, }, });