fauth 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 (51) hide show
  1. fauth-0.1.0/.github/dependabot.yml +6 -0
  2. fauth-0.1.0/.github/workflows/cicd.yml +181 -0
  3. fauth-0.1.0/.github/workflows/pre-commit-autoupdate.yml +53 -0
  4. fauth-0.1.0/.gitignore +60 -0
  5. fauth-0.1.0/.pre-commit-config.yaml +58 -0
  6. fauth-0.1.0/CHANGELOG.md +74 -0
  7. fauth-0.1.0/LICENSE +21 -0
  8. fauth-0.1.0/PKG-INFO +218 -0
  9. fauth-0.1.0/README.md +188 -0
  10. fauth-0.1.0/fauth/__init__.py +44 -0
  11. fauth-0.1.0/fauth/api/__init__.py +3 -0
  12. fauth-0.1.0/fauth/api/router.py +24 -0
  13. fauth-0.1.0/fauth/core/__init__.py +12 -0
  14. fauth-0.1.0/fauth/core/config.py +11 -0
  15. fauth-0.1.0/fauth/core/exceptions.py +19 -0
  16. fauth-0.1.0/fauth/core/schemas.py +24 -0
  17. fauth-0.1.0/fauth/crypto/__init__.py +16 -0
  18. fauth-0.1.0/fauth/crypto/jwt.py +81 -0
  19. fauth-0.1.0/fauth/crypto/password.py +14 -0
  20. fauth-0.1.0/fauth/providers/__init__.py +4 -0
  21. fauth-0.1.0/fauth/providers/protocols.py +11 -0
  22. fauth-0.1.0/fauth/providers/provider.py +161 -0
  23. fauth-0.1.0/fauth/testing/__init__.py +9 -0
  24. fauth-0.1.0/fauth/testing/config.py +16 -0
  25. fauth-0.1.0/fauth/testing/fakes.py +18 -0
  26. fauth-0.1.0/fauth/testing/provider.py +22 -0
  27. fauth-0.1.0/fauth/transports/__init__.py +4 -0
  28. fauth-0.1.0/fauth/transports/base.py +19 -0
  29. fauth-0.1.0/fauth/transports/bearer.py +20 -0
  30. fauth-0.1.0/pyproject.toml +198 -0
  31. fauth-0.1.0/pytest.ini +2 -0
  32. fauth-0.1.0/requirements.txt +52 -0
  33. fauth-0.1.0/tests/__init__.py +0 -0
  34. fauth-0.1.0/tests/api/__init__.py +0 -0
  35. fauth-0.1.0/tests/api/conftest.py +85 -0
  36. fauth-0.1.0/tests/api/test_router.py +32 -0
  37. fauth-0.1.0/tests/conftest.py +13 -0
  38. fauth-0.1.0/tests/core/__init__.py +0 -0
  39. fauth-0.1.0/tests/core/conftest.py +18 -0
  40. fauth-0.1.0/tests/core/test_config.py +16 -0
  41. fauth-0.1.0/tests/core/test_exceptions.py +19 -0
  42. fauth-0.1.0/tests/crypto/__init__.py +0 -0
  43. fauth-0.1.0/tests/crypto/conftest.py +22 -0
  44. fauth-0.1.0/tests/crypto/test_jwt.py +76 -0
  45. fauth-0.1.0/tests/crypto/test_password.py +25 -0
  46. fauth-0.1.0/tests/providers/__init__.py +0 -0
  47. fauth-0.1.0/tests/providers/conftest.py +147 -0
  48. fauth-0.1.0/tests/providers/test_provider.py +87 -0
  49. fauth-0.1.0/tests/testing/__init__.py +0 -0
  50. fauth-0.1.0/tests/testing/test_testing.py +65 -0
  51. fauth-0.1.0/uv.lock +881 -0
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: 'github-actions'
4
+ directory: '/'
5
+ schedule:
6
+ interval: 'weekly'
@@ -0,0 +1,181 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ['main']
6
+ pull_request:
7
+ branches: ['main']
8
+
9
+ permissions:
10
+ id-token: write
11
+ contents: write
12
+ actions: write
13
+
14
+ jobs:
15
+ lint:
16
+ name: Lint
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ env:
21
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
22
+ with:
23
+ token: ${{ env.GH_TOKEN }}
24
+ fetch-depth: 0
25
+
26
+ - name: Set up Python 3.13
27
+ uses: actions/setup-python@v6
28
+ with:
29
+ python-version: '3.13'
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v7
33
+ with:
34
+ version: '0.9.11'
35
+ python-version: '3.13'
36
+
37
+ - name: Install dependencies
38
+ run: |
39
+ uv sync --all-groups --frozen
40
+
41
+ - name: Run lint and format checks
42
+ id: format
43
+ run: |
44
+ uv run poe format
45
+
46
+ test:
47
+ name: Test
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - uses: actions/checkout@v6
51
+ with:
52
+ token: ${{ secrets.GH_TOKEN }}
53
+ fetch-depth: 0
54
+
55
+ - name: Set up Python 3.13
56
+ uses: actions/setup-python@v6
57
+ with:
58
+ python-version: '3.13'
59
+
60
+ - name: Install uv
61
+ uses: astral-sh/setup-uv@v7
62
+ with:
63
+ version: '0.9.11'
64
+ python-version: '3.13'
65
+
66
+ - name: Install dependencies
67
+ run: |
68
+ uv sync --all-groups --frozen
69
+
70
+ - name: Run unit tests
71
+ env:
72
+ SECRET_KEY: ${{ secrets.SECRET_KEY }}
73
+ run: |
74
+ uv run poe test
75
+
76
+ requirements:
77
+ name: Requirements
78
+ if: github.ref != 'refs/heads/main'
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@v6
82
+ with:
83
+ token: ${{ secrets.GH_TOKEN }}
84
+ fetch-depth: 0
85
+ ref: ${{ github.head_ref }}
86
+
87
+ - name: Set up Python 3.13
88
+ uses: actions/setup-python@v6
89
+ with:
90
+ python-version: '3.13'
91
+
92
+ - name: Install uv
93
+ uses: astral-sh/setup-uv@v7
94
+ with:
95
+ version: '0.9.11'
96
+ python-version: '3.13'
97
+
98
+ - name: Install dependencies
99
+ run: |
100
+ uv sync --all-groups --frozen
101
+
102
+ - name: Generate requirements.txt
103
+ run: |
104
+ uv pip compile pyproject.toml -o requirements.txt
105
+
106
+ - name: Commit and push requirements.txt
107
+ run: |
108
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
109
+ git config --local user.name "github-actions[bot]"
110
+ git add requirements.txt
111
+ git diff --staged --quiet || git commit -m "chore(config): update requirements.txt"
112
+ git push origin HEAD:${{ github.head_ref }}
113
+
114
+ versioning:
115
+ name: Versioning
116
+ runs-on: ubuntu-latest
117
+ if: github.ref == 'refs/heads/main'
118
+ needs: [lint, test]
119
+ steps:
120
+ - uses: actions/checkout@v6
121
+ env:
122
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
123
+ with:
124
+ token: ${{ env.GH_TOKEN }}
125
+ fetch-depth: 0
126
+
127
+ - name: Set up Python 3.13
128
+ uses: actions/setup-python@v6
129
+ with:
130
+ python-version: '3.13'
131
+
132
+ - name: Install uv
133
+ uses: astral-sh/setup-uv@v7
134
+ with:
135
+ version: '0.9.11'
136
+ python-version: '3.13'
137
+
138
+ - name: Install dependencies
139
+ run: |
140
+ uv sync --all-groups --frozen
141
+
142
+ - name: Bump version and update changelog
143
+ id: release
144
+ uses: python-semantic-release/python-semantic-release@v9.21.1
145
+ with:
146
+ github_token: ${{ secrets.GH_TOKEN }}
147
+
148
+ outputs:
149
+ released: ${{ steps.release.outputs.released }}
150
+
151
+ publish:
152
+ name: Publish to PyPI
153
+ runs-on: ubuntu-latest
154
+ if: needs.versioning.outputs.released == 'true'
155
+ needs: [versioning]
156
+ environment:
157
+ name: pypi
158
+ url: https://pypi.org/p/fauth
159
+ steps:
160
+ - uses: actions/checkout@v6
161
+ with:
162
+ token: ${{ secrets.GH_TOKEN }}
163
+ ref: main
164
+ fetch-depth: 0
165
+
166
+ - name: Set up Python 3.13
167
+ uses: actions/setup-python@v6
168
+ with:
169
+ python-version: '3.13'
170
+
171
+ - name: Install uv
172
+ uses: astral-sh/setup-uv@v7
173
+ with:
174
+ version: '0.9.11'
175
+ python-version: '3.13'
176
+
177
+ - name: Build package
178
+ run: uv build
179
+
180
+ - name: Publish to PyPI
181
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,53 @@
1
+ # The workflow checks out the repository, installs pre-commit, and runs the autoupdate command.
2
+ # If there are any updates, it creates a pull request with the changes.
3
+
4
+ name: Pre-commit Update
5
+
6
+ on:
7
+ schedule:
8
+ - cron: '0 2 * * *'
9
+ workflow_dispatch: # Allows manual triggering of the workflow
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ auto-update:
16
+ permissions:
17
+ contents: write
18
+ pull-requests: write
19
+
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+ - uses: actions/setup-python@v6
24
+ with:
25
+ python-version: '3.13'
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@v7
29
+ with:
30
+ version: '0.9.11'
31
+ python-version: '3.13'
32
+
33
+ - name: Install dependencies
34
+ run: |
35
+ uv sync --all-groups --frozen
36
+
37
+ - name: Autoupdate pre-commit hooks
38
+ working-directory: '.'
39
+ run: uv run pre-commit autoupdate
40
+
41
+ - name: Create Pull Request
42
+ uses: peter-evans/create-pull-request@v8
43
+ env:
44
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
45
+ with:
46
+ token: ${{ env.GH_TOKEN }}
47
+ branch: chore/pre-commit-autoupdate
48
+ title: Update pre-commit hooks
49
+ commit-message: 'chore(config): update pre-commit hooks'
50
+ body: |
51
+ This PR was created automatically by the pre-commit autoupdate workflow.
52
+ It updates the pre-commit hooks to their latest versions.
53
+ labels: update
fauth-0.1.0/.gitignore ADDED
@@ -0,0 +1,60 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+
5
+ *.so
6
+
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ *.egg-info/
21
+ *.egg
22
+ MANIFEST
23
+
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+ env/
28
+
29
+ pip-log.txt
30
+ pip-delete-this-directory.txt
31
+
32
+ htmlcov/
33
+ .tox/
34
+ .nox/
35
+ .coverage
36
+ .coverage.*
37
+ .cache
38
+ nosetests.xml
39
+ coverage.xml
40
+ *.cover
41
+ *.py,cover
42
+ .hypothesis/
43
+ .pytest_cache/
44
+
45
+ .mypy_cache/
46
+ dmypy.json
47
+ dmypy.txt
48
+ .ruff_cache/
49
+
50
+ .vscode/
51
+ .idea/
52
+ *.swp
53
+ *.swo
54
+ *~
55
+
56
+ .DS_Store
57
+ Thumbs.db
58
+
59
+ .env
60
+ .env.*
@@ -0,0 +1,58 @@
1
+ repos:
2
+ - repo: https://github.com/abravalheri/validate-pyproject
3
+ rev: v0.25
4
+ hooks:
5
+ - id: validate-pyproject
6
+
7
+ - repo: https://github.com/pre-commit/pre-commit-hooks
8
+ rev: v6.0.0
9
+ hooks:
10
+ - id: trailing-whitespace
11
+ - id: end-of-file-fixer
12
+ - id: detect-private-key
13
+
14
+ - repo: https://github.com/gitleaks/gitleaks
15
+ rev: v8.30.1
16
+ hooks:
17
+ - id: gitleaks
18
+
19
+ - repo: https://github.com/astral-sh/ruff-pre-commit
20
+ rev: 'v0.15.6'
21
+ hooks:
22
+ - id: ruff-check
23
+ name: ruff
24
+ args: ['--fix']
25
+
26
+ - repo: https://github.com/PyCQA/bandit
27
+ rev: 1.9.4
28
+ hooks:
29
+ - id: bandit
30
+ args: ['-r', '-lll']
31
+
32
+ - repo: https://github.com/pylint-dev/pylint
33
+ rev: v4.0.5
34
+ hooks:
35
+ - id: pylint
36
+ types: [python]
37
+ additional_dependencies:
38
+ - loguru==0.7.3
39
+ - pydantic==2.12.4
40
+ - pydantic-settings==2.12
41
+ - uvicorn==0.38.0
42
+ - pytest==9.0.2
43
+ - polyfactory==3.2.0
44
+ - fastapi==0.135.1
45
+
46
+ - repo: https://github.com/pre-commit/mirrors-mypy
47
+ rev: v1.19.1
48
+ hooks:
49
+ - id: mypy
50
+ additional_dependencies:
51
+ - loguru==0.7.3
52
+ - pydantic==2.12.4
53
+ - pydantic-settings==2.12
54
+ - pytest==9.0.2
55
+ - polyfactory==3.2.0
56
+ - fastapi==0.135.1
57
+ - pyjwt==2.11.0
58
+ - pwdlib==0.3.0
@@ -0,0 +1,74 @@
1
+ # CHANGELOG
2
+
3
+
4
+ ## v0.1.0 (2026-03-29)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Add tests for core and api modules
9
+ ([`cb6ed8a`](https://github.com/justmatias/fauth/commit/cb6ed8aabca0e4de41054cfd1857e1f41973d55c))
10
+
11
+ - Add tests for create_access_token
12
+ ([`eae2201`](https://github.com/justmatias/fauth/commit/eae22013a94a56e931fbd2ba21f1800b90a4fd29))
13
+
14
+ - Add tests for jwt utilities and password utilities.
15
+ ([`23f6345`](https://github.com/justmatias/fauth/commit/23f6345412be42eb6377f3bd008e4c0700b29ccb))
16
+
17
+ - Add unit tests and fixtures for the authentication provider.
18
+ ([`e81e447`](https://github.com/justmatias/fauth/commit/e81e447391307df431a419439b30c46ab6f3f0cd))
19
+
20
+ ### Chores
21
+
22
+ - Add polyfactory as new development dependencies.
23
+ ([`e36e895`](https://github.com/justmatias/fauth/commit/e36e895fff0843218efcf425c75bb6ed66e29ad6))
24
+
25
+ - Add pypi publishing job
26
+ ([`e8f5ba0`](https://github.com/justmatias/fauth/commit/e8f5ba07326dd058ed7df1f3881a7831ba563392))
27
+
28
+ - Fix lint issues
29
+ ([`8e18b2f`](https://github.com/justmatias/fauth/commit/8e18b2f4d093a05f897033c97241158dab727100))
30
+
31
+ - Improve type hints, update dependencies, and apply minor stylistic adjustments across several
32
+ modules
33
+ ([`1d55d31`](https://github.com/justmatias/fauth/commit/1d55d31cb7bd143dc353a44628c6f3575e571420))
34
+
35
+ - Restructure main package
36
+ ([`f0d7236`](https://github.com/justmatias/fauth/commit/f0d723698da27d74cb7db9c7ca3a0ef2b1e129a2))
37
+
38
+ - **config**: Merge with main
39
+ ([`c66d1eb`](https://github.com/justmatias/fauth/commit/c66d1eb612a2caebe790d20ce4a7f162ee49b6a4))
40
+
41
+ - **config**: Update pre-commit hooks
42
+ ([`77a8f95`](https://github.com/justmatias/fauth/commit/77a8f95d330ea0570a04ca8bd5bbccc72554e5e4))
43
+
44
+ - **config**: Update requirements.txt
45
+ ([`13b718a`](https://github.com/justmatias/fauth/commit/13b718aae048d817191fe26a96b6fd964f59e82a))
46
+
47
+ - **config**: Update requirements.txt
48
+ ([`ec11b90`](https://github.com/justmatias/fauth/commit/ec11b90eb30ab24a5b384c5b9662077fea46d903))
49
+
50
+ - **config**: Update requirements.txt
51
+ ([`08fed86`](https://github.com/justmatias/fauth/commit/08fed86e03f8bd6e3ec902dfc1d13225406be4a6))
52
+
53
+ ### Features
54
+
55
+ - Implement core authentication logic, transports, and testing utilities
56
+ ([`7abbd34`](https://github.com/justmatias/fauth/commit/7abbd343405c93fc86de1c454b51c94a25b04774))
57
+
58
+
59
+ ## v0.0.0 (2026-03-10)
60
+
61
+ ### Chores
62
+
63
+ - Add initial project readme and detailed design plan
64
+ ([`161b2e0`](https://github.com/justmatias/fauth/commit/161b2e0fa87b5b1d04d83496e50076362e9874f6))
65
+
66
+ - Introduce package and document new testing utilities including fakes and factories.
67
+ ([`52f3299`](https://github.com/justmatias/fauth/commit/52f32998d9095d825d7a1059be535dd7121814e6))
68
+
69
+ - Update readme
70
+ ([`1e50ee3`](https://github.com/justmatias/fauth/commit/1e50ee3d59747f19d9c54a85858c786d4a95cfbe))
71
+
72
+ - **config**: Initialize new fauth project with core structure, dependency management, CI/CD, and
73
+ code quality tooling
74
+ ([`0fe70cf`](https://github.com/justmatias/fauth/commit/0fe70cf7e453e404db44b64828ce5dc5bc440f73))
fauth-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matías Giménez
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.
fauth-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: fauth
3
+ Version: 0.1.0
4
+ Summary: Ergonomic, lightweight JWT authentication for FastAPI. Secure your routes instantly with plug-and-play dependency injection
5
+ Project-URL: Homepage, https://github.com/justmatias/fauth
6
+ Project-URL: Repository, https://github.com/justmatias/fauth
7
+ Project-URL: Issues, https://github.com/justmatias/fauth/issues
8
+ Author-email: Matias Gimenez <matiasgimenez.dev@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: authentication,fastapi,jwt,rbac,security
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Security
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: fastapi>=0.100
25
+ Requires-Dist: pwdlib[argon2]>=0.2
26
+ Requires-Dist: pydantic-settings>=2.0
27
+ Requires-Dist: pydantic>=2.0
28
+ Requires-Dist: pyjwt[crypto]>=2.8
29
+ Description-Content-Type: text/markdown
30
+
31
+ # FAuth
32
+
33
+ An ergonomic, plug-and-play authentication library for FastAPI.
34
+
35
+ `fauth` eliminates boilerplate around JWT, password hashing, user fetching, and Role-Based Access Control (RBAC) by leveraging FastAPI's Dependency Injection (`Depends`), Pydantic models, and Python Protocols.
36
+
37
+ ## Features
38
+
39
+ - **Protocol-Based User Fetching**: Complete inversion of control. You implement a simple `UserLoader` protocol to define how to fetch a user from a token payload.
40
+ - **Plug-and-Play Configuration**: Centralized settings via Pydantic (`AuthConfig`). Configure once, inject everywhere.
41
+ - **Pluggable Transports**: Extensible `Transport` protocol with a built-in `BearerTransport` for Authorization header tokens.
42
+ - **Built-in Password Hashing**: Uses modern Argon2 via `pwdlib`.
43
+ - **RBAC**: Flexible `require_roles` and `require_permissions` dependencies for endpoint authorization.
44
+ - **Secure Router**: `SecureAPIRouter` applies authentication as a router-level dependency, securing all its routes automatically.
45
+ - **Testing Utilities**: Ships fake implementations (`FakeUserLoader`) and a `build_fake_auth_provider()` factory so consumers can write unit tests with zero boilerplate.
46
+ - **Type Safety**: Fully annotated for MyPy and IDE integration.
47
+
48
+ ## Quick Start
49
+
50
+ ```bash
51
+ pip install fauth
52
+ ```
53
+
54
+ ```bash
55
+ uv add fauth
56
+ ```
57
+
58
+ ## How to use FAuth
59
+
60
+ To adopt FAuth, define an async user lookup function (implementing the `UserLoader` protocol), supply an `AuthConfig`, and instantiate the `AuthProvider`. You can then inject the provider directly into FastAPI endpoints.
61
+
62
+ ```python
63
+ from fastapi import FastAPI, Depends
64
+ from pydantic import BaseModel
65
+ from fauth import AuthConfig, AuthProvider, TokenPayload, SecureAPIRouter
66
+
67
+ app = FastAPI()
68
+
69
+ # 1. Define your internal user model
70
+ class User(BaseModel):
71
+ id: str
72
+ username: str
73
+ is_active: bool = True
74
+ roles: list[str] = []
75
+
76
+ # Mock database
77
+ DB: dict[str, User] = {
78
+ "user-123": User(id="user-123", username="alice", roles=["admin"])
79
+ }
80
+
81
+ # 2. Define the callback that retrieves a user from the decoded JWT
82
+ async def load_user(payload: TokenPayload) -> User | None:
83
+ return DB.get(payload.sub)
84
+
85
+ # 3. Instantiate the auth component (ideally wired in DI or at module-level)
86
+ config = AuthConfig(secret_key="my-super-secret-key", algorithm="HS256")
87
+ auth: AuthProvider[User] = AuthProvider(config=config, user_loader=load_user)
88
+
89
+ # --- Routes ---
90
+
91
+ @app.post("/login")
92
+ async def login():
93
+ # Example logic: password hashing checks omitted for brevity
94
+ # 4. Use `auth.login` to issue tokens
95
+ return await auth.login(sub="user-123")
96
+
97
+ @app.get("/me")
98
+ async def get_me(user: User = Depends(auth.require_user())):
99
+ # 5. `auth.require_user()` secures the endpoint automatically
100
+ return {"message": f"Hello {user.username}"}
101
+
102
+ @app.get("/admin")
103
+ async def get_admin_data(user: User = Depends(auth.require_roles("admin"))):
104
+ # 6. `auth.require_roles()` enforces RBAC implicitly
105
+ return {"secret_data": "Top secret admin info"}
106
+
107
+ # --- Securing Multiple Routes ---
108
+
109
+ # 7. Use `SecureAPIRouter` to protect an entire group of routes.
110
+ # Any route added to this router will require an active user automatically.
111
+ secure_router = SecureAPIRouter(auth_provider=auth, prefix="/internal", tags=["Protected"])
112
+
113
+ @secure_router.get("/dashboard")
114
+ async def get_dashboard():
115
+ # This endpoint is secured by FAuth without needing Depends in the signature!
116
+ return {"data": "Secure dashboard"}
117
+
118
+ app.include_router(secure_router)
119
+ ```
120
+
121
+ ## Custom Token Payload
122
+
123
+ By default, FAuth decodes JWTs into its built-in `TokenPayload` schema. If you need custom claims in your tokens (e.g., `tenant_id`, `organization_id`), subclass `TokenPayload` and pass it to `AuthProvider`:
124
+
125
+ ```python
126
+ from fauth import AuthConfig, AuthProvider, TokenPayload
127
+
128
+ class MyTokenPayload(TokenPayload):
129
+ tenant_id: str
130
+ plan: str = "free"
131
+
132
+ auth = AuthProvider(
133
+ config=AuthConfig(secret_key="my-secret"),
134
+ user_loader=load_user,
135
+ token_payload_schema=MyTokenPayload, # JWTs will be decoded into MyTokenPayload
136
+ )
137
+ ```
138
+
139
+ When issuing tokens, use the `extra` parameter to encode the custom claims into the JWT:
140
+
141
+ ```python
142
+ await auth.login(sub="user-123", extra={"tenant_id": "acme", "plan": "pro"})
143
+ ```
144
+
145
+ On the decoding side, your `user_loader` will receive a `MyTokenPayload` instance with fully typed access to `payload.tenant_id` and `payload.plan`.
146
+
147
+ ## Testing your endpoints with FAuth Fakes
148
+
149
+ FAuth ships with a `fauth.testing` module specifically to simplify testing. No complex JWT mocks or real database dependencies needed — just use `build_fake_auth_provider` to wire up an in-memory auth provider.
150
+
151
+ ```python
152
+ import asyncio
153
+ import pytest
154
+ from fastapi.testclient import TestClient
155
+ from pydantic import BaseModel
156
+
157
+ from myapp.main import app, auth # Import your FastAPI app and FAuth instance
158
+ from fauth.testing import build_fake_auth_provider
159
+
160
+ class User(BaseModel):
161
+ id: str
162
+ username: str
163
+ is_active: bool = True
164
+ roles: list[str] = []
165
+
166
+ @pytest.fixture
167
+ def test_client() -> TestClient:
168
+ # 1. Provide a mock test user
169
+ mock_user = User(id="user-123", username="test_user", roles=["admin"])
170
+
171
+ # 2. Wire the fake provider with an in-memory user store
172
+ fake = build_fake_auth_provider(users={"user-123": mock_user})
173
+
174
+ # 3. Override the internal cached dependency functions.
175
+ # These are the actual callables that FastAPI resolves inside Depends().
176
+ app.dependency_overrides[auth._require_user] = fake._require_user
177
+ app.dependency_overrides[auth._require_active_user] = fake._require_active_user
178
+
179
+ yield TestClient(app)
180
+
181
+ # Clean up overrides
182
+ app.dependency_overrides.clear()
183
+
184
+ def test_secure_route(test_client):
185
+ response = test_client.get("/me")
186
+ assert response.status_code == 200
187
+ assert response.json() == {"message": "Hello test_user"}
188
+ ```
189
+
190
+ ### Alternative: End-to-end testing with real JWT tokens
191
+
192
+ If you prefer issuing real tokens in tests, `build_fake_auth_provider` uses safe test defaults (a fixed secret key, short expiry) so you can generate tokens without any extra setup.
193
+
194
+ ```python
195
+ def test_secure_me_endpoint():
196
+ user = User(id="user-999", username="fake_alice", roles=[])
197
+
198
+ # FAuth supplies a pre-made test provider with safe defaults
199
+ test_auth = build_fake_auth_provider(users={"user-999": user})
200
+
201
+ # Generate a real JWT token via the test provider
202
+ token_response = asyncio.run(test_auth.login(sub="user-999"))
203
+
204
+ # Override the dependency so the app uses the test user store
205
+ app.dependency_overrides[auth._require_user] = test_auth._require_user
206
+
207
+ # Apply Bearer token
208
+ client = TestClient(app)
209
+ response = client.get(
210
+ "/me",
211
+ headers={"Authorization": f"Bearer {token_response.access_token}"}
212
+ )
213
+
214
+ assert response.status_code == 200
215
+ assert response.json() == {"message": "Hello fake_alice"}
216
+
217
+ app.dependency_overrides.clear()
218
+ ```