Skip to main content

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

Project description

fastapi-passkeys

fastapi-passkeys

Passkeys / WebAuthn authentication for FastAPI — phishing-resistant passwordless login with cloned-authenticator detection, single-use challenges, and strict origin checks, without locking into a specific auth library or ORM.

PyPI Python versions License: MIT CI Code style: ruff Typed: mypy strict
GitHub stars Open issues Last commit

If this project helps you, please ⭐ star the repo — it really helps others find it.  ·  Tweet


Table of contents

Status

0.1.1Alpha. Full registration + authentication ceremonies, clone detection, and SQLAlchemy/Redis adapters. The public API may change before 1.0. See CHANGELOG.md and the roadmap.

Why

Passwords are a liability and WebAuthn is easy to get subtly wrong — dropped signature counters, replayable challenges, missing origin checks. fastapi-passkeys owns the hard, security-sensitive part and stays out of the way of your idea of a session:

  • Secure by default — single-use, TTL-bound challenges; strict origin/RP validation; user-verification policy; and monotonic sign_count cloned- authenticator detection (the prior Django solution stores no counter at all).
  • Auth-agnostic — it verifies the passkey and hands you the user; you mint whatever session or JWT you like via an on_authenticated hook.
  • Storage-abstracted — credentials live behind an async CredentialRepository protocol (in-memory, stateless, SQLAlchemy 2.0, and Redis adapters included), with a shipped contract test-suite for your own.
  • Async-native and fully typed (mypy --strict, py.typed).

Install

pip install fastapi-passkeys
# optional extras:
pip install "fastapi-passkeys[sqlalchemy]"   # SQLAlchemy 2.0 async credential repo
pip install "fastapi-passkeys[redis]"        # Redis challenge store (atomic single-use)

Quickstart

from fastapi import FastAPI, Request

from fastapi_passkeys import (
    AuthenticationResult,
    Passkeys,
    PasskeyConfig,
    PasskeyUser,
)
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", tags=["passkeys"])
passkeys.install_exception_handlers(app)

Each ceremony is a beginfinish pair: begin returns the options your frontend passes to navigator.credentials.create() / .get() plus an opaque state handle; echo that state back to finish with the authenticator's response. No server session is required between the calls. A full, runnable browser demo lives in examples/app.py.

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

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 your on_authenticated hook
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
In-memory credentials fastapi_passkeys.contrib.InMemoryCredentialRepository
In-memory challenges fastapi_passkeys.contrib.InMemoryChallengeStore
Stateless challenges fastapi_passkeys.contrib.StatelessChallengeStore
SQLAlchemy credentials fastapi_passkeys.contrib.sqlalchemy.SqlAlchemyCredentialRepository [sqlalchemy]
Redis challenges fastapi_passkeys.contrib.redis.RedisChallengeStore [redis]

Implement the CredentialRepository / ChallengeStore protocols for any other store (SQLModel, Tortoise, Beanie, …) and validate it with the shipped contract suite in fastapi_passkeys.testing.

Security

  • Challenges are CSPRNG-generated, bound to user + ceremony, TTL-enforced server-side, and single-use (in-memory and Redis stores).
  • Clone detection stores and enforces the signature counter; 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 model for the full picture, including the stateless-store tradeoff. Report vulnerabilities privately via SECURITY.md.

Documentation

Full docs: https://javlondevv.github.io/fastapi-passkeys/

Roadmap

  • 0.1 — core ceremonies, clone detection, router + services, in-memory / stateless / SQLAlchemy / Redis adapters, contract suite, docs site.
  • 0.2 — username-first resolution hooks, conditional-UI helpers, attestation verification options, rate-limiting guidance.
  • 0.3 — SQLModel / Tortoise / Beanie adapters, credential metadata events, admin/management helpers.
  • 1.0 — API freeze + semver guarantee.

Contributing

Contributions are very welcome! See CONTRIBUTING.md for the dev setup and checks. Good entry points are the issues labelled good first issue and help wanted. Please also read our Code of Conduct.

Support the project

The simplest way to help is a ⭐ star — it boosts visibility for everyone.

Embed a live star button on your own site or docs with GitHub Buttons:

<!-- Place once, before </body> -->
<a class="github-button"
   href="https://github.com/javlondevv/fastapi-passkeys"
   data-icon="octicon-star"
   data-size="large"
   data-show-count="true"
   aria-label="Star javlondevv/fastapi-passkeys on GitHub">Star</a>
<script async defer src="https://buttons.github.io/buttons.js"></script>

License

MIT — see LICENSE.

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.2.tar.gz (39.9 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.2-py3-none-any.whl (40.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fastapi_passkeys-0.1.2.tar.gz
  • Upload date:
  • Size: 39.9 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.2.tar.gz
Algorithm Hash digest
SHA256 f6047a7690edbe47f11a98f3611a16132fed9b5ef4619002932de91c6bb00228
MD5 c3fba343903a036a58c810fd4359eed2
BLAKE2b-256 f4349d97dcc02c09eab20ffeb1472b0844e0b8106e8460aa2d1dd349561eeb25

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_passkeys-0.1.2.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.2-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_passkeys-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 217303f73df0a91ffbd3916d90f6497aac2409db6deb811ae6ab39aebec69e7a
MD5 799e152ff5d1827e8af98af32c179260
BLAKE2b-256 d85b872a0e949bbbd2661f8670529b78cf64691c1ccd1b43520981bf9b3fa279

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_passkeys-0.1.2-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