# How to use a Passkey (WebAuthn) server

This how-to guide will walk you through the steps to integrate Passkey (WebAuthn) server with a smart account whose user operations are relayed and sponsored by Pimlico.

:::info
Passkey (WebAuthn) is a modern authentication method that allows users to sign in to websites and apps using their fingerprint, face, or other biometric information. For more information on how Passkey works, visit [the documentation page](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API).
:::

## Steps

::::steps
### Install the required packages

```bash
npm install viem ox permissionless
```

### Create a passkeys client

You will need to create a passkeys client to interact with the passkeys server. Learn how to create your own or use passkeys server at [How to create a Passkey (WebAuthn) server](/references/permissionless/how-to/signers/passkey-server#how-to-create-a-passkey-webauthn-server).

If you want to use Pimlico's passkeys server, visit [Dashboard](https://dashboard.pimlico.io/passkey-server) and configure your passkeys server.

![](/passkey-server.png)

You will then create a passkeys client with the following code:

```ts
const passkeyServerClient = createPasskeyServerClient({
    chain,
    transport: http(
        `https://api.pimlico.io/v2/${chain.id}/rpc?apikey=${pimlicoApiKey}`
    )
})
```

### Create credentials

```tsx
import { useState } from "react"
import {
    createWebAuthnCredential,
} from "viem/account-abstraction"

export function PasskeysDemo() {
    const [credential, setCredential] = useState<P256Credential>()

    const createCredential = async () => { // [!code focus]
        const credential = await createWebAuthnCredential( // [!code focus]
            // Start the registration process // [!code focus]
            await passkeyServerClient.startRegistration({ // [!code focus]
                context: { // [!code focus]
                    // userName that will be shown to the user when creating the passkey // [!code focus]
                    userName: "plusminushalf" // [!code focus]
                } // [!code focus]
            }) // [!code focus]
        ) // [!code focus]
        // Verify the registration // [!code focus]
        const verifiedCredential = await passkeyServerClient.verifyRegistration( // [!code focus]
            { // [!code focus]
                credential, // [!code focus]
                context: { // [!code focus]
                    // userName that will be shown to the user when creating the passkey // [!code focus]
                    userName: "plusminushalf" // [!code focus]
                } // [!code focus]
            } // [!code focus]
        ) // [!code focus]
        setCredential(verifiedCredential) // [!code focus]
    } // [!code focus]

    if (!credential)
        return (
            <button type="button" onClick={createCredential}>
                Create credential
            </button>
        )

    return (
        <div>
            <p>Credential: {credential.id}</p>
        </div>
    )
}
```

### Use an existing credential

If you already have a credential, you can use it with the following code:

```tsx
const loginCredential = async () => {
    const credentials = await passkeyServerClient.getCredentials({
        context: {
            userName: "plusminushalf"
        }
    })

    // credentials is an array of credentials that match the userName
    setCredential(credentials[0])
}
```

### Create Kernel Smart Account

```tsx
import {
    type SmartAccountClient,
    createSmartAccountClient
} from "permissionless"
import {
    type ToKernelSmartAccountReturnType,
    toKernelSmartAccount
} from "permissionless/accounts"
import {
    entryPoint07Address,
    toWebAuthnAccount
} from "viem/account-abstraction"

const pimlicoUrl = `https://api.pimlico.io/v2/${chain.id}/rpc?apikey=${pimlicoApiKey}`// [!code focus]
const pimlicoClient = createPimlicoClient({ // [!code focus]
    chain, // [!code focus]
    transport: http(pimlicoUrl) // [!code focus]
}) // [!code focus]

export function PasskeysDemo() {
    const [smartAccountClient, setSmartAccountClient] =// [!code focus]
        React.useState<// [!code focus]
            SmartAccountClient<// [!code focus]
                Transport,// [!code focus]
                Chain,// [!code focus]
                ToKernelSmartAccountReturnType<"0.7">// [!code focus]
            >// [!code focus]
        >()// [!code focus]
    ...
    React.useEffect(() => { // [!code focus]
        if (!credential) return // [!code focus]
        toKernelSmartAccount({ // [!code focus]
            client: publicClient, // [!code focus]
            version: "0.3.1", // [!code focus]
            owners: [toWebAuthnAccount({ credential })], // [!code focus]
            entryPoint: { // [!code focus]
                address: entryPoint07Address, // [!code focus]
                version: "0.7" // [!code focus]
            } // [!code focus]
        }).then((account: ToKernelSmartAccountReturnType<"0.7">) => { // [!code focus]
            setSmartAccountClient( // [!code focus]
                createSmartAccountClient({ // [!code focus]
                    account, // [!code focus]
                    paymaster: pimlicoClient, // [!code focus]
                    chain, // [!code focus]
                    userOperation: { // [!code focus]
                        estimateFeesPerGas: async () => // [!code focus]
                            (await pimlicoClient.getUserOperationGasPrice()) // [!code focus]
                                .fast // [!code focus]
                    }, // [!code focus]
                    bundlerTransport: http(pimlicoUrl) // [!code focus]
                }) // [!code focus]
            ) // [!code focus]
        }) // [!code focus]
    }, [credential]) // [!code focus]
    ...
}
```

:::info
Currently, `permissionless` supports passkeys only with kernel accounts.
:::

### Send a transaction

```tsx
export function PasskeysDemo() {
    ...
    const [txHash, setTxHash] = React.useState<Hex>() // [!code focus]

    const sendUserOperation = async ( // [!code focus]
        event: React.FormEvent<HTMLFormElement> // [!code focus]
    ) => { // [!code focus]
        event.preventDefault() // [!code focus]
        if (!smartAccountClient) return // [!code focus]

        const formData = new FormData(event.currentTarget) // [!code focus]
        const to = formData.get("to") as `0x${string}` // [!code focus]
        const value = formData.get("value") as string // [!code focus]

        const txHash = await smartAccountClient.sendTransaction({ // [!code focus]
            calls: [ // [!code focus]
                { // [!code focus]
                    to, // [!code focus]
                    value: parseEther(value) // [!code focus]
                } // [!code focus]
            ], // [!code focus]
        }) // [!code focus]
        setTxHash(txHash) // [!code focus]
    } // [!code focus]

    return ( // [!code focus]
        <> // [!code focus]
            <h2>Account</h2> // [!code focus]
            <p>Address: {smartAccountClient?.account?.address}</p> // [!code focus]

            <h2>Send User Operation</h2> // [!code focus]
            <form onSubmit={sendUserOperation}> // [!code focus]
                <input name="to" placeholder="Address" /> // [!code focus]
                <input name="value" placeholder="Amount (ETH)" /> // [!code focus]
                <button type="submit">Send</button> // [!code focus]
                {txHash && <p>Transaction Hash: {txHash}</p>} // [!code focus]
            </form> // [!code focus]
        </> // [!code focus]
    ) // [!code focus]
}
```

### Sign & verify a message

```tsx
export function PasskeysDemo() {
    const [signature, setSignature] = React.useState<Hex>() // [!code focus]
    const [isVerified, setIsVerified] = React.useState<boolean>() // [!code focus]
    ...
    const signAndVerifyMessage = async () => { // [!code focus]
        if (!smartAccountClient) return // [!code focus]
        const signature = await smartAccountClient.signTypedData(typedData) // [!code focus]

        const isVerified = await publicClient.verifyTypedData({ // [!code focus]
            ...typedData, // [!code focus]
            address: smartAccountClient.account.address, // [!code focus]
            signature // [!code focus]
        }) // [!code focus]
        setIsVerified(isVerified) // [!code focus]
        setSignature(signature) // [!code focus]
    } // [!code focus]
    return ( // [!code focus]
        <> // [!code focus]
            <h2>Account</h2> // [!code focus]
            <p>Address: {smartAccountClient?.account?.address}</p> // [!code focus]

            <h2>Sign typed data</h2> // [!code focus]
            <button type="button" onClick={signAndVerifyMessage}> // [!code focus]
                Sign typed data Test // [!code focus]
            </button> // [!code focus]
            {signature && ( // [!code focus]
                <p> // [!code focus]
                    Signature: <pre>{signature}</pre> // [!code focus]
                </p> // [!code focus]
            )} // [!code focus]
            {isVerified !== undefined && ( // [!code focus]
                <p>Verified: {isVerified.toString()}</p> // [!code focus]
            )} // [!code focus]
        </> // [!code focus]
    ) // [!code focus]
}
```
::::

## How to create a Passkey (WebAuthn) server

This guide will walk you through the steps to create your own Passkey (WebAuthn) server.

::::steps
### Install the required packages

```bash
npm install @simplewebauthn/server @levischuck/tiny-cbor fastify @fastify/cors zod
```

:::info
This guide will use the `fastify` framework to create the passkeys server. You can choose to use any other framework to serve the API.
:::

### Create a server

```ts
import Fastify, { FastifyReply, FastifyRequest } from "fastify"
import cors from "@fastify/cors"
import { z } from "zod"

const app = Fastify()

// Optional: Enable CORS
app.register(cors, {
    origin: "*"
})

// Define the schema for the request
export const pksStartRegistrationRequestSchema = z.object({
    method: z.literal("pks_startRegistration"),
    params: z.tuple([
        z.object({
            userName: z.string()
        })
    ]),
    jsonrpc: z.literal("2.0"),
    id: z.number()
})
export const pksVerifyRegistrationRequestSchema = z.object({
    method: z.literal("pks_verifyRegistration"),
    params: z.tuple([
        z.object({
            id: z.string(),
            rawId: z.string(),
            response: z.object({
                clientDataJSON: z.string(),
                attestationObject: z.string(),
                authenticatorData: z.string().optional(),
                transports: z
                    .array(
                        z.enum([
                            "ble",
                            "cable",
                            "hybrid",
                            "internal",
                            "nfc",
                            "smart-card",
                            "usb"
                        ])
                    )
                    .optional(),
                publicKeyAlgorithm: z.number().optional(),
                publicKeyType: z.string().optional()
            }),
            authenticatorAttachment: z.enum(["cross-platform", "platform"]),
            clientExtensionResults: z.object({
                appid: z.boolean().optional(),
                credProps: z
                    .object({
                        rk: z.boolean().optional()
                    })
                    .optional(),
                hmacCreateSecret: z.boolean().optional()
            }),
            type: z.literal("public-key")
        }),
        z.object({
            userName: z.string()
        })
    ]),
    jsonrpc: z.literal("2.0"),
    id: z.number()
})
export const pksGetCredentialsRequestSchema = z.object({
    method: z.literal("pks_getCredentials"),
    params: z.tuple([
        z.object({
            userName: z.string()
        })
    ]),
    jsonrpc: z.literal("2.0"),
    id: z.number()
})
export const bodySchema = z.discriminatedUnion("method", [
    pksStartRegistrationRequestSchema,
    pksVerifyRegistrationRequestSchema,
    pksGetCredentialsRequestSchema
])

app.get("/rpc", async (request: FastifyRequest, reply: FastifyReply) => {
    const body = request.query as any
    
    const result = bodySchema.safeParse(body)

    if (!result.success) {
        // Invalid request
        return reply.status(400).send({
            jsonrpc: "2.0",
            id: body.id || 0,
            error: {
                code: -32600,
                message: "Invalid request"
            }
        })
    }

    if (result.data.method === "pks_startRegistration") {
        // Start registration
        return pksStartRegistrationHandler(request, reply)
    } else if (result.data.method === "pks_verifyRegistration") {
        // Verify registration
        return pksVerifyRegistrationHandler(request, reply)
    } else if (result.data.method === "pks_getCredentials") {
        // Get credentials
        return pksGetCredentialsHandler(request, reply)
    }
})
```

### Start registration

```ts
import { generateRegistrationOptions } from "@simplewebauthn/server"

const pksStartRegistrationHandler = async (
    body: z.infer<typeof pksStartRegistrationRequestSchema>
) => {
    const {
        params: [{ userName }]
    } = body

    // Validate userName
    if (!userName) {
        throw new Error("Invalid userName")
    }

    // Start registration
    const options = await generateRegistrationOptions({
        rpName: rpName,
        rpID: rpID,
        userName: userName,
        attestationType: "none",
        authenticatorSelection: {
            residentKey: "preferred",
            userVerification: "preferred",
            authenticatorAttachment: "platform"
        }
    })

    // Save options somewhere as we will need it for verification
    // ....
    // ....

    return {
        jsonrpc: "2.0",
        id: body.id,
        result: options
    }
}
```

### Verify registration

```ts
import { verifyRegistrationResponse } from "@simplewebauthn/server"
import { decodePartialCBOR } from "@levischuck/tiny-cbor"
import { PublicKey } from "ox"

const pksVerifyRegistrationHandler = async (
    body: z.infer<typeof pksVerifyRegistrationRequestSchema>
) => {
    const {
        params: [passkeysResponse, { userName }]
    } = body

    // Validate userName
    if (!userName) {
        throw new Error("Invalid userName")
    }

    // Fetch the saved options for the userName
    // ....
    // ....

    // Verify registration
    const verification = await verifyRegistrationResponse({
        response: passkeysResponse,
        expectedChallenge: savedOption.challenge,
        expectedOrigin: expectedOrigin,
        expectedRPID: savedOption.domain
    })

    // You can now delete the saved options
    // ....
    // ....

    if (!(verification.verified && verification.registrationInfo)) {
        return {
            jsonrpc: "2.0",
            id: body.id,
            error: {
                message: "Passkey verification failed"
            }
        }
    }

    const {
        registrationInfo: {
            credential,
            credentialDeviceType,
            credentialBackedUp
        }
    } = verification

    const [first] = decodePartialCBOR(credential.publicKey, 0) as [
        Map<number, Uint8Array> | undefined,
        number
    ]

    if (!first) {
        return {
            jsonrpc: "2.0",
            id: body.id,
            error: {
                message: "Invalid public key"
            }
        }
    }

    const publicKey = {
        prefix: 4,
        x: first.get(-2),
        y: first.get(-3)
    }

    if (!(publicKey.x && publicKey.y)) {
        return {
            jsonrpc: "2.0",
            id: body.id,
            error: {
                message: "Invalid public key"
            }
        }
    }

    const newPasskey = {
        userName: userName,
        webAuthnUserID: savedOption.user.id,
        id: credential.id,
        publicKey: PublicKey.toHex(
            PublicKey.from(
                new Uint8Array([0x04, ...publicKey.x, ...publicKey.y])
            )
        ),
        counter: credential.counter,
        transports: credential.transports?.join(","),
        deviceType: credentialDeviceType,
        backedUp: credentialBackedUp
    }

    // Save the new passkey
    // ....
    // ....

    return {
        jsonrpc: "2.0",
        id: body.id,
        result: {
            success: verification.verified,
            id: newPasskey.id,
            publicKey: newPasskey.publicKey
        }
    }
}
```

### Get credentials

```ts
const pksGetCredentialsHandler = (
    body: z.infer<typeof pksGetCredentialsRequestSchema>
) => {
    const {
        params: [{ userName }]
    } = body

    // Get saved passkeys for the userName
    // ....
    // ....

    return {
        jsonrpc: "2.0",
        id: body.id,
        result: savedPasskeys.map((passkey) => ({
            id: passkey.id,
            publicKey: passkey.publicKey
        }))
    }
}

```
::::
