Skip to main content

Passkeys / WebAuthn authentication for FastAPI — secure by default, storage-agnostic, async-native.

Project description

fastapi-passkeys

PyPI Python License: MIT CI

Passkeys / WebAuthn authentication for FastAPI — secure by default, storage-agnostic, async-native.

fastapi-passkeys owns the hard, security-sensitive part of passkeys — the WebAuthn ceremony, challenge integrity, signature-counter clone detection, and credential lifecycle — and stays out of the way of your idea of a session. You bring a storage backend and two small hooks; it brings correctness.

Status: alpha (0.1.x). The public API may change before 1.0. It is being driven to a stable, production-ready 1.0 — see the roadmap.

Table of contents

Why

Passwords are a liability. Passkeys (WebAuthn) replace them with phishing-resistant, public-key credentials backed by the user's device. But WebAuthn is easy to get subtly wrong: dropped signature counters, replayable challenges, missing origin checks. The existing Django solution couples tightly to the Django ORM, sessions, and DRF, and notably stores no signature counter at all — so it cannot detect cloned authenticators.

fastapi-passkeys is a ground-up design for FastAPI that:

  • Is secure by default — enforced challenge TTL + single-use, origin/RP validation, user-verification policy, and monotonic sign_count clone detection.
  • Doesn't lock you into an ORM — storage is an async Protocol; ship-with adapters for SQLAlchemy and Redis, plus a contract test-suite for your own.
  • Doesn't impose an auth opinion — it verifies the passkey and hands you the user; you mint whatever session or token you like.
  • Is fully typed (mypy --strict) and async end-to-end.

Install

pip install fastapi-passkeys                  # core (in-memory + stateless stores)
pip install "fastapi-passkeys[sqlalchemy]"    # SQLAlchemy 2.0 async credential repo
pip install "fastapi-passkeys[redis]"         # Redis challenge store

Quickstart

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 (session, signup token, ...)
    return PasskeyUser(id="user-123", name="ada@example.com", display_name="Ada Lovelace")


async def on_authenticated(request: Request, result: AuthenticationResult) -> dict:
    # mint your own session / JWT here
    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)

Need full control? Skip the router and drive passkeys.registration / passkeys.authentication (the services) from your own endpoints.

How it works

Both ceremonies are two HTTP round-trips. begin returns the options your frontend passes to navigator.credentials.create() / .get(), plus an opaque state handle. Your frontend echoes that state back to finish along with the authenticator's response. The challenge is single-use and TTL-bound; the handle is the only thing the client needs to keep between the two calls — no server session required.

Endpoints

Method Path Purpose
POST /register/begin Start registration; returns creation options + state.
POST /register/finish Verify attestation and store the credential.
POST /authenticate/begin Start authentication (usernameless or with userId).
POST /authenticate/finish Verify assertion; runs on_authenticated.
GET /credentials List the current user's passkeys.
PATCH /credentials/{id} Rename a passkey.
DELETE /credentials/{id} Revoke a passkey.

Storage backends

Backend Import Extra Notes
In-memory fastapi_passkeys.contrib.InMemoryCredentialRepository Dev/tests, single process.
In-memory challenges fastapi_passkeys.contrib.InMemoryChallengeStore Single-use, single process.
Stateless challenges fastapi_passkeys.contrib.StatelessChallengeStore Signed token, no infra (tradeoff).
SQLAlchemy fastapi_passkeys.contrib.sqlalchemy.SqlAlchemyCredentialRepository [sqlalchemy] Async, production default.
Redis challenges fastapi_passkeys.contrib.redis.RedisChallengeStore [redis] Atomic single-use across instances.

Implement the CredentialRepository / ChallengeStore Protocols for any other store and validate it with the shipped contract suite (fastapi_passkeys.testing).

Security model

  • Challenges are CSPRNG-generated, bound to user + ceremony, TTL-enforced server-side, and single-use (in the in-memory and Redis stores).
  • Clone detection: the signature counter is stored and required to advance; a regression is rejected (and optionally auto-disables the credential) and audited.
  • Origins & RP ID are strictly validated; multiple origins are supported.
  • No secrets are logged; audit events carry identifiers and outcomes only.

See the security documentation for the full model.

Roadmap

  • Core domain, engine, services
  • Registration & authentication flows
  • SQLAlchemy + Redis adapters, contract suite
  • Documentation site
  • 1.0.0 once the public API is proven stable

Contributing

Issues and PRs welcome. Run ruff check, mypy src, and pytest before submitting. See CONTRIBUTING.md.

License

MIT © Javlon Baxtiyorov

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_passkeys-0.1.1.tar.gz (37.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_passkeys-0.1.1-py3-none-any.whl (39.2 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_passkeys-0.1.1.tar.gz.

File metadata

  • Download URL: fastapi_passkeys-0.1.1.tar.gz
  • Upload date:
  • Size: 37.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_passkeys-0.1.1.tar.gz
Algorithm Hash digest
SHA256 5d58724683c8a7ea02a4d8e61814d75a885be77a15921db2093980e91756dba3
MD5 7e8453a29000d6aea72dff264ca84bcd
BLAKE2b-256 bc2d029b6fcff82e6d1e9443c15d42956fea1be6d6a69095e430fe2f22856f01

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_passkeys-0.1.1.tar.gz:

Publisher: release.yml on javlondevv/fastapi-passkeys

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_passkeys-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_passkeys-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a66ffecf6eaf6cd359fee9eec3c844e5edb25c32e975084bfee51eda98b2934a
MD5 ce5a362b4845e9f3a1a6d9ed6c74b28f
BLAKE2b-256 0a6a0a577f4ddb0d3ff01d4dfdd74722fd6018a3c3c49cfb3b2498ffc2a3178d

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_passkeys-0.1.1-py3-none-any.whl:

Publisher: release.yml on javlondevv/fastapi-passkeys

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page