Passkeys / WebAuthn authentication for FastAPI — secure by default, storage-agnostic, async-native.
Project description
fastapi-passkeys
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
- Install
- Quickstart
- How it works
- Endpoints
- Storage backends
- Security model
- Roadmap
- Contributing
- License
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_countclone 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.0once 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d58724683c8a7ea02a4d8e61814d75a885be77a15921db2093980e91756dba3
|
|
| MD5 |
7e8453a29000d6aea72dff264ca84bcd
|
|
| BLAKE2b-256 |
bc2d029b6fcff82e6d1e9443c15d42956fea1be6d6a69095e430fe2f22856f01
|
Provenance
The following attestation bundles were made for fastapi_passkeys-0.1.1.tar.gz:
Publisher:
release.yml on javlondevv/fastapi-passkeys
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_passkeys-0.1.1.tar.gz -
Subject digest:
5d58724683c8a7ea02a4d8e61814d75a885be77a15921db2093980e91756dba3 - Sigstore transparency entry: 1863029864
- Sigstore integration time:
-
Permalink:
javlondevv/fastapi-passkeys@cd1eb35d0bb55b0c223986c3f03ef1a07bb26f83 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/javlondevv
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cd1eb35d0bb55b0c223986c3f03ef1a07bb26f83 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fastapi_passkeys-0.1.1-py3-none-any.whl.
File metadata
- Download URL: fastapi_passkeys-0.1.1-py3-none-any.whl
- Upload date:
- Size: 39.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a66ffecf6eaf6cd359fee9eec3c844e5edb25c32e975084bfee51eda98b2934a
|
|
| MD5 |
ce5a362b4845e9f3a1a6d9ed6c74b28f
|
|
| BLAKE2b-256 |
0a6a0a577f4ddb0d3ff01d4dfdd74722fd6018a3c3c49cfb3b2498ffc2a3178d
|
Provenance
The following attestation bundles were made for fastapi_passkeys-0.1.1-py3-none-any.whl:
Publisher:
release.yml on javlondevv/fastapi-passkeys
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_passkeys-0.1.1-py3-none-any.whl -
Subject digest:
a66ffecf6eaf6cd359fee9eec3c844e5edb25c32e975084bfee51eda98b2934a - Sigstore transparency entry: 1863030081
- Sigstore integration time:
-
Permalink:
javlondevv/fastapi-passkeys@cd1eb35d0bb55b0c223986c3f03ef1a07bb26f83 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/javlondevv
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cd1eb35d0bb55b0c223986c3f03ef1a07bb26f83 -
Trigger Event:
push
-
Statement type: