Production-grade, async-native authentication and authorization library for FastAPI
Project description
FastAPI FullAuth
Production-grade, async-native authentication and authorization for FastAPI.
Documentation: https://mdfarhankc.github.io/fastapi-fullauth
Source Code: https://github.com/mdfarhankc/fastapi-fullauth
A complete, async-native authentication and authorization system for FastAPI - production-ready and pluggable. JWT access/refresh tokens with rotation, Argon2 password hashing, email verification, OAuth2 social login, passkeys, session management, and role-based access control, all out of the box. Bring your own database with the SQLModel or SQLAlchemy adapter, and opt into only the features you need.
Features
- JWT access + refresh tokens with configurable expiry
- Refresh token rotation with reuse detection; revokes entire session family on replay
- Session management: list active sessions (device, IP, last used), revoke one device, or sign out everywhere else
- Password hashing via Argon2id (default) or bcrypt, with transparent rehashing
- Email verification and password reset flows with event hooks
- Passkey (WebAuthn): passwordless login with fingerprint, Face ID, security keys
- OAuth2 social login: Google and GitHub, with multi-redirect-URI support
- Role-based access control:
current_user,require_role(),require_permission() - Rate limiting: per-route auth limits + global middleware (memory or Redis)
- CSRF protection and security headers middleware
- Bearer or cookie transport: opt into HttpOnly cookies that carry both access and refresh tokens, out of JavaScript's reach; bearer is the default
- Pluggable adapters: SQLModel, SQLAlchemy, or write your own for any data store
- Generic type parameters: define your own schemas with full IDE support and type safety
- Composable routers: include only the route groups you need
- Event hooks:
after_register,after_login,send_verification_email, etc. - Custom JWT claims: embed app-specific data in tokens
- Structured logging: all auth events, security violations, and failures logged
- Redis support: token blacklist and rate limiter backends
- Python 3.10 - 3.14 supported
Installation
The fastest way to start: one adapter plus every optional feature in a single extra.
# Recommended: SQLModel adapter + Redis, OAuth, passkeys, bcrypt
pip install "fastapi-fullauth[sqlmodel-standard]"
# Same, on the SQLAlchemy adapter
pip install "fastapi-fullauth[sqlalchemy-standard]"
Or stay minimal and add only what you use:
# Core + one ORM adapter (pick one)
pip install "fastapi-fullauth[sqlmodel]"
# Mix and match any extras
pip install "fastapi-fullauth[sqlalchemy,oauth,redis]"
| Extra | Adds |
|---|---|
sqlmodel / sqlalchemy |
ORM adapter + Alembic (pick one) |
redis |
Redis backends for token blacklist, lockout, rate limiting, passkey challenges |
oauth |
OAuth2 social login (Google, GitHub) |
passkey |
Passkey / WebAuthn support |
bcrypt |
bcrypt password hashing (Argon2id is the default and needs no extra) |
sqlmodel-standard / sqlalchemy-standard |
One adapter plus all of the above |
Quotes around the package spec keep shells like zsh from globbing the
[extras].
Quick start
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlmodel import Relationship
from fastapi_fullauth import FullAuth, FullAuthConfig
from fastapi_fullauth.adapters import SQLModelAdapter
from fastapi_fullauth.models.sqlmodel import RefreshTokenMixin, UserMixin
class RefreshToken(RefreshTokenMixin, table=True):
pass
class User(UserMixin, table=True):
refresh_tokens: list[RefreshToken] = Relationship()
engine = create_async_engine("sqlite+aiosqlite:///./app.db")
session_maker = async_sessionmaker(engine, expire_on_commit=False)
app = FastAPI()
fullauth = FullAuth(
adapter=SQLModelAdapter(
session_maker=session_maker,
user_model=User,
refresh_token_model=RefreshToken,
),
config=FullAuthConfig(SECRET_KEY="your-secret-key"),
)
fullauth.init_app(app)
That's it; all auth routes are registered under /api/v1/auth/ automatically. Create the tables with Alembic (see the migrations guide) or SQLModel.metadata.create_all for a quick local start.
The SQLite quick-start needs an async driver:
pip install aiosqlite.
Omit config in dev and a random secret key is generated (tokens won't survive restarts).
Composable routers
Opt in to a subset of routers:
fullauth.init_app(app, include_routers=["auth", "profile"])
include_routers=None (default) registers every available router. Or wire routers manually for full control:
app = FastAPI()
fullauth.bind(app) # required for dependencies to work
app.include_router(fullauth.auth_router, prefix="/api/v1/auth")
app.include_router(fullauth.profile_router, prefix="/api/v1/auth")
| Router | Routes |
|---|---|
auth_router |
register, login, logout, refresh |
profile_router |
me, verified-me, update profile, delete account, change password |
verify_router |
email verification, password reset |
admin_router |
assign/remove roles and permissions (superuser) |
oauth_router |
OAuth provider routes (only if configured) |
passkey_router |
Passkey register, authenticate, list, delete (only if enabled) |
sessions_router |
list active sessions, revoke one device, sign out others |
Middleware
init_app() does not wire any middleware automatically. Import what you want and add it yourself:
from fastapi_fullauth.middleware import (
SecurityHeadersMiddleware,
CSRFMiddleware,
RateLimitMiddleware,
)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(CSRFMiddleware, secret=fullauth.config.SECRET_KEY)
app.add_middleware(RateLimitMiddleware, max_requests=60, window_seconds=60)
Token transport
By default, tokens are returned in the response body for Authorization: Bearer use. Opt into cookie transport to carry both the access and refresh tokens in separate HttpOnly cookies, out of JavaScript's reach:
from fastapi_fullauth.backends import CookieBackend
fullauth = FullAuth(
adapter=adapter,
config=config,
backends=[CookieBackend(config)],
)
/refresh and /logout then read the tokens from the cookies, so the browser never stores them. Wire CSRFMiddleware whenever you use cookie transport.
Routes
| Method | Path | Description |
|---|---|---|
POST |
/auth/register |
Create a new user |
POST |
/auth/login |
Authenticate, get tokens |
POST |
/auth/logout |
Blacklist token |
POST |
/auth/refresh |
Rotate token pair |
GET |
/auth/sessions |
List active sessions |
DELETE |
/auth/sessions/{family_id} |
Revoke one session |
POST |
/auth/sessions/revoke-others |
Sign out other sessions |
GET |
/auth/me |
Get current user |
GET |
/auth/me/verified |
Verified users only |
PATCH |
/auth/me |
Update profile |
DELETE |
/auth/me |
Delete account |
POST |
/auth/change-password |
Change password |
POST |
/auth/verify-email/request |
Request verification email |
POST |
/auth/verify-email/confirm |
Confirm email |
POST |
/auth/password-reset/request |
Request password reset |
POST |
/auth/password-reset/confirm |
Reset password |
POST |
/auth/admin/assign-role |
Assign role (superuser) |
POST |
/auth/admin/remove-role |
Remove role (superuser) |
POST |
/auth/admin/assign-permission |
Assign permission to role (superuser) |
POST |
/auth/admin/remove-permission |
Remove permission from role (superuser) |
GET |
/auth/admin/role-permissions/{role_name} |
List role's permissions (superuser) |
With OAuth enabled, additional routes are registered under /auth/oauth/. All routes are prefixed with /api/v1 by default.
Custom user schemas
Combine each mixin with table=True (or your DeclarativeBase for the SQLAlchemy adapter), then pass everything to the adapter:
from sqlmodel import Field, Relationship
from fastapi_fullauth import FullAuth, FullAuthConfig, UserSchema, CreateUserSchema
from fastapi_fullauth.adapters import SQLModelAdapter
from fastapi_fullauth.models.sqlmodel import (
RefreshTokenMixin, RoleMixin, UserMixin, UserRoleMixin,
)
class RefreshToken(RefreshTokenMixin, table=True): pass
class Role(RoleMixin, table=True): pass
class UserRole(UserRoleMixin, table=True): pass
class User(UserMixin, table=True):
display_name: str = Field(default="", max_length=100)
phone: str = Field(default="", max_length=20)
roles: list[Role] = Relationship(link_model=UserRole)
refresh_tokens: list[RefreshToken] = Relationship()
class MyUserSchema(UserSchema):
display_name: str = ""
phone: str = ""
class MyCreateSchema(CreateUserSchema):
display_name: str = ""
fullauth = FullAuth(
adapter=SQLModelAdapter(
session_maker,
user_model=User,
refresh_token_model=RefreshToken,
role_model=Role,
user_role_model=UserRole,
user_schema=MyUserSchema,
create_user_schema=MyCreateSchema,
),
config=FullAuthConfig(SECRET_KEY="..."),
)
Full IDE autocompletion and type checking on custom fields:
from typing import Annotated
from fastapi import Depends
from fastapi_fullauth.dependencies import current_user, current_active_verified_user
CurrentUser = Annotated[MyUserSchema, Depends(current_user)]
VerifiedUser = Annotated[MyUserSchema, Depends(current_active_verified_user)]
@app.get("/profile")
async def profile(user: CurrentUser):
return {"name": user.display_name} # IDE knows this field exists
Protected routes
from typing import Annotated
from fastapi import Depends
from fastapi_fullauth.dependencies import current_user, current_active_verified_user, current_superuser, require_role
CurrentUser = Annotated[UserSchema, Depends(current_user)]
VerifiedUser = Annotated[UserSchema, Depends(current_active_verified_user)]
SuperUser = Annotated[UserSchema, Depends(current_superuser)]
@app.get("/profile")
async def profile(user: CurrentUser):
return user
@app.get("/dashboard")
async def dashboard(user: VerifiedUser):
return {"email": user.email}
@app.delete("/admin/users/{id}")
async def delete_user(user: SuperUser):
...
@app.get("/editor")
async def editor_panel(user=Depends(require_role("editor"))):
...
OAuth2 social login
from fastapi_fullauth import FullAuth, FullAuthConfig
from fastapi_fullauth.oauth.google import GoogleOAuthProvider
from fastapi_fullauth.oauth.github import GitHubOAuthProvider
fullauth = FullAuth(
adapter=adapter,
config=FullAuthConfig(SECRET_KEY="..."),
providers=[
GoogleOAuthProvider(
client_id="your-google-client-id",
client_secret="your-google-secret",
redirect_uris=[
"http://localhost:3000/auth/callback",
"https://myapp.com/auth/callback",
],
),
GitHubOAuthProvider(
client_id="your-github-client-id",
client_secret="your-github-secret",
redirect_uris=["http://localhost:3000/auth/callback"],
),
],
)
Requires httpx: pip install "fastapi-fullauth[oauth]"
Security note: the OAuth
statetoken is signed and carries the PKCE challenge, but it is not bound to the browser session. For defense against login-CSRF, also bindstateto the initiating browser (e.g. a short-lived cookie you set before redirecting and verify on callback).
Event hooks
async def welcome(user):
await send_email(user.email, "Welcome!")
async def send_verify(email, token):
await send_email(email, f"Verify: https://myapp.com/verify?token={token}")
fullauth.hooks.on("after_register", welcome)
fullauth.hooks.on("send_verification_email", send_verify)
Events: after_register, after_login, after_logout, after_password_change, after_password_reset, after_email_verify, send_verification_email, send_password_reset_email, after_oauth_login, after_oauth_register
Configuration
Pass a FullAuthConfig object or set env vars with FULLAUTH_ prefix.
fullauth = FullAuth(
adapter=adapter,
config=FullAuthConfig(
SECRET_KEY="...",
ACCESS_TOKEN_EXPIRE_MINUTES=60,
API_PREFIX="/api/v2",
LOGIN_FIELD="username",
PASSWORD_HASH_ALGORITHM="bcrypt",
BLACKLIST_BACKEND="redis",
REDIS_URL="redis://localhost:6379/0",
AUTH_RATE_LIMIT_ENABLED=True,
TRUSTED_PROXY_HEADERS=["X-Forwarded-For"],
),
)
See Configuration docs for all options.
AI-friendly docs
Using an AI coding assistant? Point it at our LLM-optimized docs:
- llms.txt: concise overview with links to all doc pages
- llms-full.txt: full documentation in a single file
Works with Claude, Cursor, Copilot, and any tool that accepts a docs URL.
Development
git clone https://github.com/mdfarhankc/fastapi-fullauth.git
cd fastapi-fullauth
uv sync --dev --extra sqlalchemy --extra sqlmodel
uv run pytest tests/ -v
# run examples
uv run uvicorn examples.sqlmodel_app.main:app --reload
License
MIT
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_fullauth-0.14.1.tar.gz.
File metadata
- Download URL: fastapi_fullauth-0.14.1.tar.gz
- Upload date:
- Size: 362.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08675a3ef66fc984c252ff9f4fe3f94831df888d90c2034bd949ae161ef4a92b
|
|
| MD5 |
3549a6da473fef612a73f4154fb7220a
|
|
| BLAKE2b-256 |
0d82ed63c964198a96e71ada864910d8e863f69dffb2dce4382564448204a9b7
|
Provenance
The following attestation bundles were made for fastapi_fullauth-0.14.1.tar.gz:
Publisher:
publish.yml on mdfarhankc/fastapi-fullauth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_fullauth-0.14.1.tar.gz -
Subject digest:
08675a3ef66fc984c252ff9f4fe3f94831df888d90c2034bd949ae161ef4a92b - Sigstore transparency entry: 1821886461
- Sigstore integration time:
-
Permalink:
mdfarhankc/fastapi-fullauth@2f494c05f76b107ac44c454c7ec5efe155243d3e -
Branch / Tag:
refs/tags/v0.14.1 - Owner: https://github.com/mdfarhankc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f494c05f76b107ac44c454c7ec5efe155243d3e -
Trigger Event:
release
-
Statement type:
File details
Details for the file fastapi_fullauth-0.14.1-py3-none-any.whl.
File metadata
- Download URL: fastapi_fullauth-0.14.1-py3-none-any.whl
- Upload date:
- Size: 148.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 |
236ba4b5a5a5c5add214d4e3dfe0cd6ce09388f89dd8ea7263229f65570a4c47
|
|
| MD5 |
ab150e3fa8bd95ad56bcc50078a097c4
|
|
| BLAKE2b-256 |
6668ee6f2a73e4b59544ebb2b75654dcaba33dcba2ca28e64e6463305a19680a
|
Provenance
The following attestation bundles were made for fastapi_fullauth-0.14.1-py3-none-any.whl:
Publisher:
publish.yml on mdfarhankc/fastapi-fullauth
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_fullauth-0.14.1-py3-none-any.whl -
Subject digest:
236ba4b5a5a5c5add214d4e3dfe0cd6ce09388f89dd8ea7263229f65570a4c47 - Sigstore transparency entry: 1821886486
- Sigstore integration time:
-
Permalink:
mdfarhankc/fastapi-fullauth@2f494c05f76b107ac44c454c7ec5efe155243d3e -
Branch / Tag:
refs/tags/v0.14.1 - Owner: https://github.com/mdfarhankc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2f494c05f76b107ac44c454c7ec5efe155243d3e -
Trigger Event:
release
-
Statement type: