fastapi-fullauth 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_fullauth-0.1.0/.github/workflows/ci.yml +32 -0
- fastapi_fullauth-0.1.0/.github/workflows/publish.yml +20 -0
- fastapi_fullauth-0.1.0/.gitignore +34 -0
- fastapi_fullauth-0.1.0/.python-version +1 -0
- fastapi_fullauth-0.1.0/CHANGELOG.md +38 -0
- fastapi_fullauth-0.1.0/LICENSE +21 -0
- fastapi_fullauth-0.1.0/PKG-INFO +225 -0
- fastapi_fullauth-0.1.0/README.md +159 -0
- fastapi_fullauth-0.1.0/examples/memory_app.py +62 -0
- fastapi_fullauth-0.1.0/examples/sqlalchemy_app.py +86 -0
- fastapi_fullauth-0.1.0/examples/sqlmodel_app.py +91 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/__init__.py +16 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/__init__.py +27 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/base.py +57 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/memory.py +98 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/__init__.py +17 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/adapter.py +240 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/models.py +71 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/__init__.py +17 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/adapter.py +209 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/models.py +74 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/backends/__init__.py +5 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/backends/base.py +14 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/backends/bearer.py +19 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/backends/cookie.py +29 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/config.py +66 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/core/__init__.py +4 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/core/crypto.py +19 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/core/redis_blacklist.py +26 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/core/tokens.py +104 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/__init__.py +22 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/current_user.py +84 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/require_role.py +78 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/exceptions.py +67 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/__init__.py +15 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/email_verify.py +48 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/login.py +60 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/logout.py +18 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/password_reset.py +46 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/flows/register.py +17 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/fullauth.py +189 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/hooks.py +16 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/__init__.py +5 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/csrf.py +117 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/ratelimit.py +3 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/security_headers.py +26 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/migrations/__init__.py +3 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/migrations/helpers.py +61 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/protection/__init__.py +4 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/protection/lockout.py +33 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/protection/ratelimit.py +86 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/rbac/__init__.py +0 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/router/__init__.py +0 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/router/auth.py +477 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/types.py +65 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/utils.py +23 -0
- fastapi_fullauth-0.1.0/fastapi_fullauth/validators.py +45 -0
- fastapi_fullauth-0.1.0/pyproject.toml +104 -0
- fastapi_fullauth-0.1.0/tests/__init__.py +0 -0
- fastapi_fullauth-0.1.0/tests/conftest.py +72 -0
- fastapi_fullauth-0.1.0/tests/test_auth_flows.py +177 -0
- fastapi_fullauth-0.1.0/tests/test_crypto.py +23 -0
- fastapi_fullauth-0.1.0/tests/test_customization.py +372 -0
- fastapi_fullauth-0.1.0/tests/test_dx_improvements.py +322 -0
- fastapi_fullauth-0.1.0/tests/test_email_verify.py +119 -0
- fastapi_fullauth-0.1.0/tests/test_lockout.py +52 -0
- fastapi_fullauth-0.1.0/tests/test_middleware.py +125 -0
- fastapi_fullauth-0.1.0/tests/test_new_endpoints.py +283 -0
- fastapi_fullauth-0.1.0/tests/test_refresh_tokens.py +326 -0
- fastapi_fullauth-0.1.0/tests/test_roles.py +161 -0
- fastapi_fullauth-0.1.0/tests/test_tokens.py +64 -0
- fastapi_fullauth-0.1.0/uv.lock +1465 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
- run: uv sync --dev
|
|
18
|
+
- run: uv run ruff check .
|
|
19
|
+
- run: uv run ruff format --check .
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: ${{ matrix.python-version }}
|
|
31
|
+
- run: uv sync --dev --extra sqlalchemy --extra sqlmodel
|
|
32
|
+
- run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.13"
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Databases
|
|
13
|
+
*.db
|
|
14
|
+
*.sqlite3
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
|
|
22
|
+
# OS
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
|
|
26
|
+
# Testing
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
|
|
31
|
+
# Distribution
|
|
32
|
+
*.tar.gz
|
|
33
|
+
*.whl
|
|
34
|
+
architecture.md
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Core auth engine**: JWT access/refresh tokens with rotation and blacklisting
|
|
8
|
+
- **Password hashing**: Argon2id by default via argon2-cffi
|
|
9
|
+
- **Auth flows**: register, login, logout, password reset, email verification
|
|
10
|
+
- **Brute-force protection**: progressive lockout after configurable failed attempts
|
|
11
|
+
- **Auth backends**: Bearer token and HttpOnly cookie backends (pluggable)
|
|
12
|
+
- **FastAPI dependencies**: `current_user`, `current_active_verified_user`, `require_role`, `require_permission`, `CurrentUser`, `VerifiedUser`
|
|
13
|
+
- **Pre-built router**: `/auth/me` (GET/PATCH/DELETE), `/auth/me/verified`, `/auth/register`, `/auth/login`, `/auth/logout`, `/auth/refresh`, `/auth/change-password`, `/auth/password-reset/*`, `/auth/verify-email/*`, `/auth/admin/*`
|
|
14
|
+
- **Change password**: `POST /auth/change-password` — verifies current password, validates new password strength
|
|
15
|
+
- **Update profile**: `PATCH /auth/me` — update user fields with protected field filtering
|
|
16
|
+
- **Delete account**: `DELETE /auth/me` — self-deletion for logged-in users
|
|
17
|
+
- **Token expires_in**: login and refresh responses include `expires_in` (seconds) for frontend token refresh scheduling
|
|
18
|
+
- **Auth route rate limiting**: per-IP rate limits on login, register, and password-reset routes (configurable via `AUTH_RATE_LIMIT_*`)
|
|
19
|
+
- **Flat config**: `FullAuth(secret_key=..., adapter=...)` — no `FullAuthConfig` wrapper needed
|
|
20
|
+
- **Auto SECRET_KEY**: omit `secret_key` in dev mode, auto-generates with a warning
|
|
21
|
+
- **Route enum**: `Route.LOGIN`, `Route.ME`, etc. for type-safe `enabled_routes`
|
|
22
|
+
- **Auto-derive schemas**: `UserSchema` and `CreateUserSchema` auto-generated from ORM model fields
|
|
23
|
+
- **Auto-wire middleware**: SecurityHeaders, CSRF, and RateLimit auto-added by `init_app()` from config flags
|
|
24
|
+
- **Email hooks**: `send_verification_email` and `send_password_reset_email` in the hooks system
|
|
25
|
+
- **Event hooks**: `after_register`, `after_login`, `after_logout`, `after_password_reset`, `after_email_verify`
|
|
26
|
+
- **Redis blacklist**: async `RedisBlacklist` backend via `redis.asyncio` — activate with `BLACKLIST_BACKEND="redis"`
|
|
27
|
+
- **Refresh token persistence**: stored in DB with family tracking for theft detection
|
|
28
|
+
- **Token reuse detection**: replaying a used refresh token revokes the entire token family
|
|
29
|
+
- **Logout refresh revocation**: pass `refresh_token` in logout body to revoke the session family
|
|
30
|
+
- **Configuration**: `FullAuthConfig` via pydantic-settings with `FULLAUTH_` env var prefix, or inline kwargs
|
|
31
|
+
- **ORM adapters**: SQLAlchemy (async), SQLModel (async), and InMemory (for testing)
|
|
32
|
+
- **Modular extras**: `[sqlalchemy]`, `[sqlmodel]`, `[redis]` — install only what you need
|
|
33
|
+
- **Alembic migration helpers**: `include_fullauth_models()` and `get_fullauth_metadata()`
|
|
34
|
+
- **Password validation**: configurable rules (length, uppercase, digit, special, blocked list)
|
|
35
|
+
- **Custom token claims**: `on_create_token_claims` callback embedded in JWTs
|
|
36
|
+
- **Utilities**: `create_superuser()`, `generate_secret_key()`
|
|
37
|
+
- **Test suite**: 97 tests covering all auth flows, tokens, middleware, DX, refresh token security, and new endpoints
|
|
38
|
+
- **Examples**: InMemory, SQLAlchemy, and SQLModel demo apps
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammed Farhan KC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-fullauth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Production-grade, async-native authentication and authorization library for FastAPI
|
|
5
|
+
Project-URL: Homepage, https://github.com/mdfarhankc/fastapi-fullauth
|
|
6
|
+
Project-URL: Documentation, https://github.com/mdfarhankc/fastapi-fullauth
|
|
7
|
+
Project-URL: Repository, https://github.com/mdfarhankc/fastapi-fullauth
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: argon2,authentication,authorization,fastapi,jwt,security
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: argon2-cffi>=23.0
|
|
24
|
+
Requires-Dist: fastapi>=0.110
|
|
25
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
26
|
+
Requires-Dist: pydantic[email]>=2.0
|
|
27
|
+
Requires-Dist: pyjwt>=2.8
|
|
28
|
+
Requires-Dist: python-multipart>=0.0.22
|
|
29
|
+
Requires-Dist: uuid-utils>=0.14.1
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: alembic>=1.13; extra == 'all'
|
|
32
|
+
Requires-Dist: beanie>=1.25; extra == 'all'
|
|
33
|
+
Requires-Dist: httpx-oauth>=0.13; extra == 'all'
|
|
34
|
+
Requires-Dist: itsdangerous>=2.1; extra == 'all'
|
|
35
|
+
Requires-Dist: pyotp>=2.9; extra == 'all'
|
|
36
|
+
Requires-Dist: qrcode>=7.4; extra == 'all'
|
|
37
|
+
Requires-Dist: redis>=5.0; extra == 'all'
|
|
38
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'all'
|
|
39
|
+
Requires-Dist: sqlmodel>=0.0.16; extra == 'all'
|
|
40
|
+
Requires-Dist: tortoise-orm>=0.21; extra == 'all'
|
|
41
|
+
Requires-Dist: webauthn>=2.0; extra == 'all'
|
|
42
|
+
Provides-Extra: audit
|
|
43
|
+
Provides-Extra: beanie
|
|
44
|
+
Requires-Dist: beanie>=1.25; extra == 'beanie'
|
|
45
|
+
Provides-Extra: magic-link
|
|
46
|
+
Requires-Dist: itsdangerous>=2.1; extra == 'magic-link'
|
|
47
|
+
Provides-Extra: oauth
|
|
48
|
+
Requires-Dist: httpx-oauth>=0.13; extra == 'oauth'
|
|
49
|
+
Provides-Extra: passkeys
|
|
50
|
+
Requires-Dist: webauthn>=2.0; extra == 'passkeys'
|
|
51
|
+
Provides-Extra: redis
|
|
52
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
53
|
+
Provides-Extra: sqlalchemy
|
|
54
|
+
Requires-Dist: alembic>=1.13; extra == 'sqlalchemy'
|
|
55
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'sqlalchemy'
|
|
56
|
+
Provides-Extra: sqlmodel
|
|
57
|
+
Requires-Dist: alembic>=1.13; extra == 'sqlmodel'
|
|
58
|
+
Requires-Dist: sqlmodel>=0.0.16; extra == 'sqlmodel'
|
|
59
|
+
Provides-Extra: tenants
|
|
60
|
+
Provides-Extra: tortoise
|
|
61
|
+
Requires-Dist: tortoise-orm>=0.21; extra == 'tortoise'
|
|
62
|
+
Provides-Extra: totp
|
|
63
|
+
Requires-Dist: pyotp>=2.9; extra == 'totp'
|
|
64
|
+
Requires-Dist: qrcode>=7.4; extra == 'totp'
|
|
65
|
+
Description-Content-Type: text/markdown
|
|
66
|
+
|
|
67
|
+
# fastapi-fullauth
|
|
68
|
+
|
|
69
|
+
[](https://pypi.org/project/fastapi-fullauth/)
|
|
70
|
+
[](https://pypi.org/project/fastapi-fullauth/)
|
|
71
|
+
[](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml)
|
|
72
|
+
[](https://opensource.org/licenses/MIT)
|
|
73
|
+
|
|
74
|
+
Async auth library for FastAPI. Handles JWT tokens, refresh rotation, password hashing, email verification, and role-based access out of the box.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install fastapi-fullauth
|
|
80
|
+
# with an ORM adapter:
|
|
81
|
+
pip install fastapi-fullauth[sqlmodel]
|
|
82
|
+
pip install fastapi-fullauth[sqlalchemy]
|
|
83
|
+
# with redis for token blacklisting:
|
|
84
|
+
pip install fastapi-fullauth[sqlmodel,redis]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from fastapi import FastAPI
|
|
91
|
+
from fastapi_fullauth import FullAuth
|
|
92
|
+
from fastapi_fullauth.adapters.memory import InMemoryAdapter
|
|
93
|
+
|
|
94
|
+
app = FastAPI()
|
|
95
|
+
|
|
96
|
+
fullauth = FullAuth(
|
|
97
|
+
secret_key="your-secret-key",
|
|
98
|
+
adapter=InMemoryAdapter(),
|
|
99
|
+
)
|
|
100
|
+
fullauth.init_app(app)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This gives you `/auth/me`, `/auth/register`, `/auth/login`, `/auth/logout`, `/auth/refresh`, `/auth/change-password`, `/auth/password-reset/*`, `/auth/verify-email/*`, and admin role management endpoints — all under `/api/v1` by default.
|
|
104
|
+
|
|
105
|
+
Omit `secret_key` in dev and a random one is generated (tokens won't survive restarts).
|
|
106
|
+
|
|
107
|
+
## Custom user fields
|
|
108
|
+
|
|
109
|
+
Just define your model — schemas are auto-derived:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from fastapi_fullauth.adapters.sqlmodel import UserBase, Role, UserRoleLink, RefreshTokenRecord, SQLModelAdapter
|
|
113
|
+
from sqlmodel import Field, Relationship
|
|
114
|
+
|
|
115
|
+
class MyUser(UserBase, table=True):
|
|
116
|
+
__tablename__ = "fullauth_users"
|
|
117
|
+
__table_args__ = {"extend_existing": True}
|
|
118
|
+
|
|
119
|
+
display_name: str = Field(default="", max_length=100)
|
|
120
|
+
phone: str = Field(default="", max_length=20)
|
|
121
|
+
|
|
122
|
+
roles: list[Role] = Relationship(back_populates="users", link_model=UserRoleLink)
|
|
123
|
+
refresh_tokens: list[RefreshTokenRecord] = Relationship(back_populates="user")
|
|
124
|
+
|
|
125
|
+
fullauth = FullAuth(
|
|
126
|
+
secret_key="...",
|
|
127
|
+
adapter=SQLModelAdapter(session_maker, user_model=MyUser),
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
No need to create separate schema classes or subclass the adapter. Registration and response schemas pick up `display_name` and `phone` automatically. You can still pass explicit `user_schema` / `create_user_schema` if you want full control.
|
|
132
|
+
|
|
133
|
+
## Protected routes
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from fastapi import Depends
|
|
137
|
+
from fastapi_fullauth.dependencies import current_user, require_role
|
|
138
|
+
|
|
139
|
+
@app.get("/profile")
|
|
140
|
+
async def profile(user=Depends(current_user)):
|
|
141
|
+
return user
|
|
142
|
+
|
|
143
|
+
@app.delete("/admin/users/{id}")
|
|
144
|
+
async def delete_user(user=Depends(require_role("admin"))):
|
|
145
|
+
...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
Pass inline kwargs or a full config object:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# inline
|
|
154
|
+
fullauth = FullAuth(
|
|
155
|
+
secret_key="...",
|
|
156
|
+
adapter=adapter,
|
|
157
|
+
api_prefix="/api/v2",
|
|
158
|
+
access_token_expire_minutes=60,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# or use FullAuthConfig for everything
|
|
162
|
+
from fastapi_fullauth import FullAuthConfig
|
|
163
|
+
fullauth = FullAuth(config=FullAuthConfig(SECRET_KEY="..."), adapter=adapter)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Config also reads env vars with `FULLAUTH_` prefix.
|
|
167
|
+
|
|
168
|
+
## Redis blacklist
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
fullauth = FullAuth(
|
|
172
|
+
secret_key="...",
|
|
173
|
+
adapter=adapter,
|
|
174
|
+
blacklist_backend="redis",
|
|
175
|
+
redis_url="redis://localhost:6379/0",
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Refresh token security
|
|
180
|
+
|
|
181
|
+
Refresh tokens are stored in DB with family tracking. If a revoked token is replayed (possible theft), the entire token family gets revoked. Disable rotation with `REFRESH_TOKEN_ROTATION=False`.
|
|
182
|
+
|
|
183
|
+
## Event hooks
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
async def welcome(user):
|
|
187
|
+
await send_email(user.email, "Welcome!")
|
|
188
|
+
|
|
189
|
+
fullauth.hooks.on("after_register", welcome)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Events: `after_register`, `after_login`, `after_logout`, `after_password_change`, `after_password_reset`, `after_email_verify`, `send_verification_email`, `send_password_reset_email`
|
|
193
|
+
|
|
194
|
+
## Route control
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from fastapi_fullauth import Route
|
|
198
|
+
|
|
199
|
+
fullauth = FullAuth(
|
|
200
|
+
secret_key="...",
|
|
201
|
+
adapter=adapter,
|
|
202
|
+
enabled_routes=[Route.LOGIN, Route.LOGOUT, Route.REFRESH],
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Middleware
|
|
207
|
+
|
|
208
|
+
SecurityHeaders, CSRF, and rate limiting are auto-wired from config flags. Pass `auto_middleware=False` to `init_app()` to handle it yourself.
|
|
209
|
+
|
|
210
|
+
## Auth rate limiting
|
|
211
|
+
|
|
212
|
+
Login, register, and password-reset have per-IP rate limits enabled by default (5/3/3 per minute). Configure via `AUTH_RATE_LIMIT_*` settings.
|
|
213
|
+
|
|
214
|
+
## Development
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
git clone https://github.com/mdfarhankc/fastapi-fullauth.git
|
|
218
|
+
cd fastapi-fullauth
|
|
219
|
+
uv sync --dev --extra sqlalchemy --extra sqlmodel
|
|
220
|
+
uv run pytest tests/ -v
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# fastapi-fullauth
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/fastapi-fullauth/)
|
|
4
|
+
[](https://pypi.org/project/fastapi-fullauth/)
|
|
5
|
+
[](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Async auth library for FastAPI. Handles JWT tokens, refresh rotation, password hashing, email verification, and role-based access out of the box.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install fastapi-fullauth
|
|
14
|
+
# with an ORM adapter:
|
|
15
|
+
pip install fastapi-fullauth[sqlmodel]
|
|
16
|
+
pip install fastapi-fullauth[sqlalchemy]
|
|
17
|
+
# with redis for token blacklisting:
|
|
18
|
+
pip install fastapi-fullauth[sqlmodel,redis]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from fastapi import FastAPI
|
|
25
|
+
from fastapi_fullauth import FullAuth
|
|
26
|
+
from fastapi_fullauth.adapters.memory import InMemoryAdapter
|
|
27
|
+
|
|
28
|
+
app = FastAPI()
|
|
29
|
+
|
|
30
|
+
fullauth = FullAuth(
|
|
31
|
+
secret_key="your-secret-key",
|
|
32
|
+
adapter=InMemoryAdapter(),
|
|
33
|
+
)
|
|
34
|
+
fullauth.init_app(app)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This gives you `/auth/me`, `/auth/register`, `/auth/login`, `/auth/logout`, `/auth/refresh`, `/auth/change-password`, `/auth/password-reset/*`, `/auth/verify-email/*`, and admin role management endpoints — all under `/api/v1` by default.
|
|
38
|
+
|
|
39
|
+
Omit `secret_key` in dev and a random one is generated (tokens won't survive restarts).
|
|
40
|
+
|
|
41
|
+
## Custom user fields
|
|
42
|
+
|
|
43
|
+
Just define your model — schemas are auto-derived:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from fastapi_fullauth.adapters.sqlmodel import UserBase, Role, UserRoleLink, RefreshTokenRecord, SQLModelAdapter
|
|
47
|
+
from sqlmodel import Field, Relationship
|
|
48
|
+
|
|
49
|
+
class MyUser(UserBase, table=True):
|
|
50
|
+
__tablename__ = "fullauth_users"
|
|
51
|
+
__table_args__ = {"extend_existing": True}
|
|
52
|
+
|
|
53
|
+
display_name: str = Field(default="", max_length=100)
|
|
54
|
+
phone: str = Field(default="", max_length=20)
|
|
55
|
+
|
|
56
|
+
roles: list[Role] = Relationship(back_populates="users", link_model=UserRoleLink)
|
|
57
|
+
refresh_tokens: list[RefreshTokenRecord] = Relationship(back_populates="user")
|
|
58
|
+
|
|
59
|
+
fullauth = FullAuth(
|
|
60
|
+
secret_key="...",
|
|
61
|
+
adapter=SQLModelAdapter(session_maker, user_model=MyUser),
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
No need to create separate schema classes or subclass the adapter. Registration and response schemas pick up `display_name` and `phone` automatically. You can still pass explicit `user_schema` / `create_user_schema` if you want full control.
|
|
66
|
+
|
|
67
|
+
## Protected routes
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fastapi import Depends
|
|
71
|
+
from fastapi_fullauth.dependencies import current_user, require_role
|
|
72
|
+
|
|
73
|
+
@app.get("/profile")
|
|
74
|
+
async def profile(user=Depends(current_user)):
|
|
75
|
+
return user
|
|
76
|
+
|
|
77
|
+
@app.delete("/admin/users/{id}")
|
|
78
|
+
async def delete_user(user=Depends(require_role("admin"))):
|
|
79
|
+
...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
Pass inline kwargs or a full config object:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# inline
|
|
88
|
+
fullauth = FullAuth(
|
|
89
|
+
secret_key="...",
|
|
90
|
+
adapter=adapter,
|
|
91
|
+
api_prefix="/api/v2",
|
|
92
|
+
access_token_expire_minutes=60,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# or use FullAuthConfig for everything
|
|
96
|
+
from fastapi_fullauth import FullAuthConfig
|
|
97
|
+
fullauth = FullAuth(config=FullAuthConfig(SECRET_KEY="..."), adapter=adapter)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Config also reads env vars with `FULLAUTH_` prefix.
|
|
101
|
+
|
|
102
|
+
## Redis blacklist
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
fullauth = FullAuth(
|
|
106
|
+
secret_key="...",
|
|
107
|
+
adapter=adapter,
|
|
108
|
+
blacklist_backend="redis",
|
|
109
|
+
redis_url="redis://localhost:6379/0",
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Refresh token security
|
|
114
|
+
|
|
115
|
+
Refresh tokens are stored in DB with family tracking. If a revoked token is replayed (possible theft), the entire token family gets revoked. Disable rotation with `REFRESH_TOKEN_ROTATION=False`.
|
|
116
|
+
|
|
117
|
+
## Event hooks
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
async def welcome(user):
|
|
121
|
+
await send_email(user.email, "Welcome!")
|
|
122
|
+
|
|
123
|
+
fullauth.hooks.on("after_register", welcome)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Events: `after_register`, `after_login`, `after_logout`, `after_password_change`, `after_password_reset`, `after_email_verify`, `send_verification_email`, `send_password_reset_email`
|
|
127
|
+
|
|
128
|
+
## Route control
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from fastapi_fullauth import Route
|
|
132
|
+
|
|
133
|
+
fullauth = FullAuth(
|
|
134
|
+
secret_key="...",
|
|
135
|
+
adapter=adapter,
|
|
136
|
+
enabled_routes=[Route.LOGIN, Route.LOGOUT, Route.REFRESH],
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Middleware
|
|
141
|
+
|
|
142
|
+
SecurityHeaders, CSRF, and rate limiting are auto-wired from config flags. Pass `auto_middleware=False` to `init_app()` to handle it yourself.
|
|
143
|
+
|
|
144
|
+
## Auth rate limiting
|
|
145
|
+
|
|
146
|
+
Login, register, and password-reset have per-IP rate limits enabled by default (5/3/3 per minute). Configure via `AUTH_RATE_LIMIT_*` settings.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/mdfarhankc/fastapi-fullauth.git
|
|
152
|
+
cd fastapi-fullauth
|
|
153
|
+
uv sync --dev --extra sqlalchemy --extra sqlmodel
|
|
154
|
+
uv run pytest tests/ -v
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-memory example — no database needed.
|
|
3
|
+
|
|
4
|
+
Run: uv run uvicorn examples.memory_app:app --reload
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import Depends, FastAPI
|
|
8
|
+
|
|
9
|
+
from fastapi_fullauth import FullAuth
|
|
10
|
+
from fastapi_fullauth.adapters.memory import InMemoryAdapter
|
|
11
|
+
from fastapi_fullauth.dependencies import (
|
|
12
|
+
current_active_verified_user,
|
|
13
|
+
current_user,
|
|
14
|
+
require_role,
|
|
15
|
+
)
|
|
16
|
+
from fastapi_fullauth.types import CreateUserSchema, UserSchema
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MyCreateUserSchema(CreateUserSchema):
|
|
20
|
+
display_name: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# replace these with real email sending in production
|
|
24
|
+
async def send_verification_email(email: str, token: str):
|
|
25
|
+
print(f"\n[VERIFY] To: {email}\n[VERIFY] Token: {token}\n")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def send_password_reset_email(email: str, token: str):
|
|
29
|
+
print(f"\n[RESET] To: {email}\n[RESET] Token: {token}\n")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def add_custom_claims(user: UserSchema) -> dict:
|
|
33
|
+
return {"display_name": getattr(user, "display_name", "")}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
app = FastAPI(title="FullAuth In-Memory Demo")
|
|
37
|
+
|
|
38
|
+
fullauth = FullAuth(
|
|
39
|
+
secret_key="change-me-use-a-32-byte-key-here",
|
|
40
|
+
adapter=InMemoryAdapter(),
|
|
41
|
+
on_send_verification_email=send_verification_email,
|
|
42
|
+
on_send_password_reset_email=send_password_reset_email,
|
|
43
|
+
create_user_schema=MyCreateUserSchema,
|
|
44
|
+
on_create_token_claims=add_custom_claims,
|
|
45
|
+
include_user_in_login=True,
|
|
46
|
+
)
|
|
47
|
+
fullauth.init_app(app)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.get("/api/v1/me")
|
|
51
|
+
async def me(user=Depends(current_user)):
|
|
52
|
+
return user
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.get("/api/v1/verified-only")
|
|
56
|
+
async def verified_only(user=Depends(current_active_verified_user)):
|
|
57
|
+
return {"msg": "your email is verified", "user": user}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.get("/api/v1/admin")
|
|
61
|
+
async def admin_only(user=Depends(require_role("admin"))):
|
|
62
|
+
return {"msg": "welcome admin", "user": user}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQLAlchemy example with custom user fields.
|
|
3
|
+
|
|
4
|
+
Run: uv run uvicorn examples.sqlalchemy_app:app --reload
|
|
5
|
+
Requires: uv add fastapi-fullauth[sqlalchemy] aiosqlite
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
|
|
10
|
+
from fastapi import Depends, FastAPI
|
|
11
|
+
from sqlalchemy import String
|
|
12
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
13
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
14
|
+
|
|
15
|
+
from fastapi_fullauth import FullAuth
|
|
16
|
+
from fastapi_fullauth.adapters.sqlalchemy import (
|
|
17
|
+
FullAuthBase,
|
|
18
|
+
SQLAlchemyAdapter,
|
|
19
|
+
UserModel,
|
|
20
|
+
)
|
|
21
|
+
from fastapi_fullauth.dependencies import (
|
|
22
|
+
current_active_verified_user,
|
|
23
|
+
current_user,
|
|
24
|
+
require_role,
|
|
25
|
+
)
|
|
26
|
+
from fastapi_fullauth.types import UserSchema
|
|
27
|
+
|
|
28
|
+
DATABASE_URL = "sqlite+aiosqlite:///fullauth_sqlalchemy_demo.db"
|
|
29
|
+
engine = create_async_engine(DATABASE_URL)
|
|
30
|
+
session_maker = async_sessionmaker(engine, expire_on_commit=False)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MyUser(UserModel):
|
|
34
|
+
__tablename__ = "fullauth_users"
|
|
35
|
+
__table_args__ = {"extend_existing": True}
|
|
36
|
+
|
|
37
|
+
display_name: Mapped[str] = mapped_column(String(100), nullable=True, default="")
|
|
38
|
+
phone: Mapped[str] = mapped_column(String(20), nullable=True, default="")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def send_verification_email(email: str, token: str):
|
|
42
|
+
print(f"\n[VERIFY] To: {email}\n[VERIFY] Token: {token}\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def send_password_reset_email(email: str, token: str):
|
|
46
|
+
print(f"\n[RESET] To: {email}\n[RESET] Token: {token}\n")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def add_custom_claims(user: UserSchema) -> dict:
|
|
50
|
+
return {"display_name": getattr(user, "display_name", "")}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@asynccontextmanager
|
|
54
|
+
async def lifespan(app: FastAPI):
|
|
55
|
+
async with engine.begin() as conn:
|
|
56
|
+
await conn.run_sync(FullAuthBase.metadata.create_all)
|
|
57
|
+
yield
|
|
58
|
+
await engine.dispose()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
app = FastAPI(title="FullAuth SQLAlchemy Demo", lifespan=lifespan)
|
|
62
|
+
|
|
63
|
+
fullauth = FullAuth(
|
|
64
|
+
secret_key="change-me-use-a-32-byte-key-here",
|
|
65
|
+
adapter=SQLAlchemyAdapter(session_maker=session_maker, user_model=MyUser),
|
|
66
|
+
on_send_verification_email=send_verification_email,
|
|
67
|
+
on_send_password_reset_email=send_password_reset_email,
|
|
68
|
+
on_create_token_claims=add_custom_claims,
|
|
69
|
+
include_user_in_login=True,
|
|
70
|
+
)
|
|
71
|
+
fullauth.init_app(app)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.get("/api/v1/me")
|
|
75
|
+
async def me(user=Depends(current_user)):
|
|
76
|
+
return user
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.get("/api/v1/verified-only")
|
|
80
|
+
async def verified_only(user=Depends(current_active_verified_user)):
|
|
81
|
+
return {"msg": "your email is verified", "user": user}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.get("/api/v1/admin")
|
|
85
|
+
async def admin_only(user=Depends(require_role("admin"))):
|
|
86
|
+
return {"msg": "welcome admin", "user": user}
|