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.
Files changed (72) hide show
  1. fastapi_fullauth-0.1.0/.github/workflows/ci.yml +32 -0
  2. fastapi_fullauth-0.1.0/.github/workflows/publish.yml +20 -0
  3. fastapi_fullauth-0.1.0/.gitignore +34 -0
  4. fastapi_fullauth-0.1.0/.python-version +1 -0
  5. fastapi_fullauth-0.1.0/CHANGELOG.md +38 -0
  6. fastapi_fullauth-0.1.0/LICENSE +21 -0
  7. fastapi_fullauth-0.1.0/PKG-INFO +225 -0
  8. fastapi_fullauth-0.1.0/README.md +159 -0
  9. fastapi_fullauth-0.1.0/examples/memory_app.py +62 -0
  10. fastapi_fullauth-0.1.0/examples/sqlalchemy_app.py +86 -0
  11. fastapi_fullauth-0.1.0/examples/sqlmodel_app.py +91 -0
  12. fastapi_fullauth-0.1.0/fastapi_fullauth/__init__.py +16 -0
  13. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/__init__.py +27 -0
  14. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/base.py +57 -0
  15. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/memory.py +98 -0
  16. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/__init__.py +17 -0
  17. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/adapter.py +240 -0
  18. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlalchemy/models.py +71 -0
  19. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/__init__.py +17 -0
  20. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/adapter.py +209 -0
  21. fastapi_fullauth-0.1.0/fastapi_fullauth/adapters/sqlmodel/models.py +74 -0
  22. fastapi_fullauth-0.1.0/fastapi_fullauth/backends/__init__.py +5 -0
  23. fastapi_fullauth-0.1.0/fastapi_fullauth/backends/base.py +14 -0
  24. fastapi_fullauth-0.1.0/fastapi_fullauth/backends/bearer.py +19 -0
  25. fastapi_fullauth-0.1.0/fastapi_fullauth/backends/cookie.py +29 -0
  26. fastapi_fullauth-0.1.0/fastapi_fullauth/config.py +66 -0
  27. fastapi_fullauth-0.1.0/fastapi_fullauth/core/__init__.py +4 -0
  28. fastapi_fullauth-0.1.0/fastapi_fullauth/core/crypto.py +19 -0
  29. fastapi_fullauth-0.1.0/fastapi_fullauth/core/redis_blacklist.py +26 -0
  30. fastapi_fullauth-0.1.0/fastapi_fullauth/core/tokens.py +104 -0
  31. fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/__init__.py +22 -0
  32. fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/current_user.py +84 -0
  33. fastapi_fullauth-0.1.0/fastapi_fullauth/dependencies/require_role.py +78 -0
  34. fastapi_fullauth-0.1.0/fastapi_fullauth/exceptions.py +67 -0
  35. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/__init__.py +15 -0
  36. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/email_verify.py +48 -0
  37. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/login.py +60 -0
  38. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/logout.py +18 -0
  39. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/password_reset.py +46 -0
  40. fastapi_fullauth-0.1.0/fastapi_fullauth/flows/register.py +17 -0
  41. fastapi_fullauth-0.1.0/fastapi_fullauth/fullauth.py +189 -0
  42. fastapi_fullauth-0.1.0/fastapi_fullauth/hooks.py +16 -0
  43. fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/__init__.py +5 -0
  44. fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/csrf.py +117 -0
  45. fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/ratelimit.py +3 -0
  46. fastapi_fullauth-0.1.0/fastapi_fullauth/middleware/security_headers.py +26 -0
  47. fastapi_fullauth-0.1.0/fastapi_fullauth/migrations/__init__.py +3 -0
  48. fastapi_fullauth-0.1.0/fastapi_fullauth/migrations/helpers.py +61 -0
  49. fastapi_fullauth-0.1.0/fastapi_fullauth/protection/__init__.py +4 -0
  50. fastapi_fullauth-0.1.0/fastapi_fullauth/protection/lockout.py +33 -0
  51. fastapi_fullauth-0.1.0/fastapi_fullauth/protection/ratelimit.py +86 -0
  52. fastapi_fullauth-0.1.0/fastapi_fullauth/rbac/__init__.py +0 -0
  53. fastapi_fullauth-0.1.0/fastapi_fullauth/router/__init__.py +0 -0
  54. fastapi_fullauth-0.1.0/fastapi_fullauth/router/auth.py +477 -0
  55. fastapi_fullauth-0.1.0/fastapi_fullauth/types.py +65 -0
  56. fastapi_fullauth-0.1.0/fastapi_fullauth/utils.py +23 -0
  57. fastapi_fullauth-0.1.0/fastapi_fullauth/validators.py +45 -0
  58. fastapi_fullauth-0.1.0/pyproject.toml +104 -0
  59. fastapi_fullauth-0.1.0/tests/__init__.py +0 -0
  60. fastapi_fullauth-0.1.0/tests/conftest.py +72 -0
  61. fastapi_fullauth-0.1.0/tests/test_auth_flows.py +177 -0
  62. fastapi_fullauth-0.1.0/tests/test_crypto.py +23 -0
  63. fastapi_fullauth-0.1.0/tests/test_customization.py +372 -0
  64. fastapi_fullauth-0.1.0/tests/test_dx_improvements.py +322 -0
  65. fastapi_fullauth-0.1.0/tests/test_email_verify.py +119 -0
  66. fastapi_fullauth-0.1.0/tests/test_lockout.py +52 -0
  67. fastapi_fullauth-0.1.0/tests/test_middleware.py +125 -0
  68. fastapi_fullauth-0.1.0/tests/test_new_endpoints.py +283 -0
  69. fastapi_fullauth-0.1.0/tests/test_refresh_tokens.py +326 -0
  70. fastapi_fullauth-0.1.0/tests/test_roles.py +161 -0
  71. fastapi_fullauth-0.1.0/tests/test_tokens.py +64 -0
  72. 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
+ [![PyPI](https://img.shields.io/pypi/v/fastapi-fullauth)](https://pypi.org/project/fastapi-fullauth/)
70
+ [![Python](https://img.shields.io/pypi/pyversions/fastapi-fullauth)](https://pypi.org/project/fastapi-fullauth/)
71
+ [![CI](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml/badge.svg)](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml)
72
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/fastapi-fullauth)](https://pypi.org/project/fastapi-fullauth/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/fastapi-fullauth)](https://pypi.org/project/fastapi-fullauth/)
5
+ [![CI](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml/badge.svg)](https://github.com/mdfarhankc/fastapi-fullauth/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](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}