Skip to content

Quickstart

Install

pip install fastapi-passkeys

Wire it up

You provide two hooks and a storage backend; the library provides the router.

from fastapi import FastAPI, Request
from fastapi_passkeys import Passkeys, PasskeyConfig, PasskeyUser, AuthenticationResult
from fastapi_passkeys.contrib import InMemoryCredentialRepository


async def get_user(request: Request) -> PasskeyUser:
    # However your app identifies the in-progress user: a signup token, an
    # existing session, an email from the request body, etc.
    return PasskeyUser(id="user-123", name="ada@example.com", display_name="Ada Lovelace")


async def on_authenticated(request: Request, result: AuthenticationResult) -> dict:
    # The passkey is verified. Now mint *your* session or token.
    return {"access_token": issue_token(result.user_id)}


passkeys = Passkeys(
    config=PasskeyConfig(
        rp_id="example.com",
        rp_name="Example",
        expected_origins=["https://example.com"],
    ),
    credential_repository=InMemoryCredentialRepository(),
    get_user=get_user,
    on_authenticated=on_authenticated,
)

app = FastAPI()
app.include_router(passkeys.router, prefix="/auth/passkeys")
passkeys.install_exception_handlers(app)

The two round-trips

Each ceremony is begin then finish. begin returns the options your frontend passes to navigator.credentials.create() / .get(), plus an opaque state handle. Echo that state back to finish alongside the authenticator's response. The challenge is single-use and TTL-bound — no server session is required between the calls.

POST /auth/passkeys/register/begin      -> { publicKey, state }
POST /auth/passkeys/register/finish     <- { credential, state, deviceName? }

POST /auth/passkeys/authenticate/begin  -> { publicKey, state }
POST /auth/passkeys/authenticate/finish <- { credential, state }

A complete, runnable browser example lives in examples/app.py.

Need full control?

Skip the router and drive the services directly:

options, state = await passkeys.registration.begin(user)
credential = await passkeys.registration.finish(response=response, handle=state)