Passkeys / WebAuthn authentication for FastAPI — secure by default, storage-agnostic, async-native.
Project description
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.
If this project helps you, please ⭐ star the repo — it really helps others find it.
·
Table of contents
- Status
- Why
- Install
- Quickstart
- Endpoints
- Storage backends
- Security
- Documentation
- Roadmap
- Contributing
- Support the project
- License
Status
0.1.1 — Alpha. 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_countcloned- 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_authenticatedhook. - Storage-abstracted — credentials live behind an async
CredentialRepositoryprotocol (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 begin → finish 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/
- Quickstart
- Concepts — a short WebAuthn primer
- Storage backends
- Security model
- Migrating from django-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
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.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6047a7690edbe47f11a98f3611a16132fed9b5ef4619002932de91c6bb00228
|
|
| MD5 |
c3fba343903a036a58c810fd4359eed2
|
|
| BLAKE2b-256 |
f4349d97dcc02c09eab20ffeb1472b0844e0b8106e8460aa2d1dd349561eeb25
|
Provenance
The following attestation bundles were made for fastapi_passkeys-0.1.2.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.2.tar.gz -
Subject digest:
f6047a7690edbe47f11a98f3611a16132fed9b5ef4619002932de91c6bb00228 - Sigstore transparency entry: 1863294947
- Sigstore integration time:
-
Permalink:
javlondevv/fastapi-passkeys@14ed2c4a4734b1d713dfa54441bd13da65789946 -
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@14ed2c4a4734b1d713dfa54441bd13da65789946 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fastapi_passkeys-0.1.2-py3-none-any.whl.
File metadata
- Download URL: fastapi_passkeys-0.1.2-py3-none-any.whl
- Upload date:
- Size: 40.3 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 |
217303f73df0a91ffbd3916d90f6497aac2409db6deb811ae6ab39aebec69e7a
|
|
| MD5 |
799e152ff5d1827e8af98af32c179260
|
|
| BLAKE2b-256 |
d85b872a0e949bbbd2661f8670529b78cf64691c1ccd1b43520981bf9b3fa279
|
Provenance
The following attestation bundles were made for fastapi_passkeys-0.1.2-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.2-py3-none-any.whl -
Subject digest:
217303f73df0a91ffbd3916d90f6497aac2409db6deb811ae6ab39aebec69e7a - Sigstore transparency entry: 1863295081
- Sigstore integration time:
-
Permalink:
javlondevv/fastapi-passkeys@14ed2c4a4734b1d713dfa54441bd13da65789946 -
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@14ed2c4a4734b1d713dfa54441bd13da65789946 -
Trigger Event:
push
-
Statement type: