Skip to content

javlondevv/fastapi-passkeys

Repository files navigation

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 version 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.

About

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

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages