fastapi-multiauth 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_multiauth-0.1.0/LICENSE +21 -0
- fastapi_multiauth-0.1.0/PKG-INFO +161 -0
- fastapi_multiauth-0.1.0/README.md +120 -0
- fastapi_multiauth-0.1.0/pyproject.toml +101 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/__init__.py +42 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/_http.py +44 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/_imports.py +9 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/abc.py +220 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/exceptions.py +66 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/jwt.py +260 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/oauth/__init__.py +43 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/oauth/client.py +246 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/oauth/utils.py +159 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/py.typed +0 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/__init__.py +17 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/basic.py +68 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/bearer.py +80 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/cookie.py +153 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/header.py +45 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/multi.py +97 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/sources/query.py +50 -0
- fastapi_multiauth-0.1.0/src/fastapi_multiauth/utils.py +83 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 d3vyce
|
|
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,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-multiauth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes, correct OpenAPI by construction
|
|
5
|
+
Keywords: fastapi,authentication,security,oauth,api-key,bearer,cookie
|
|
6
|
+
Author: d3vyce
|
|
7
|
+
Author-email: d3vyce <contact@d3vyce.fr>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Dist: anyio>=3.7
|
|
27
|
+
Requires-Dist: async-lru>=2.0
|
|
28
|
+
Requires-Dist: fastapi>=0.100.0
|
|
29
|
+
Requires-Dist: httpx>=0.25.0
|
|
30
|
+
Requires-Dist: itsdangerous>=2.1
|
|
31
|
+
Requires-Dist: pyjwt[crypto]>=2.8 ; extra == 'jwt'
|
|
32
|
+
Requires-Dist: httpx-oauth>=0.16 ; extra == 'oauth'
|
|
33
|
+
Requires-Python: >=3.10
|
|
34
|
+
Project-URL: Homepage, https://github.com/d3vyce/fastapi-multiauth
|
|
35
|
+
Project-URL: Documentation, https://fastapi-multiauth.d3vyce.fr/
|
|
36
|
+
Project-URL: Repository, https://github.com/d3vyce/fastapi-multiauth
|
|
37
|
+
Project-URL: Issues, https://github.com/d3vyce/fastapi-multiauth/issues
|
|
38
|
+
Provides-Extra: jwt
|
|
39
|
+
Provides-Extra: oauth
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# FastAPI MultiAuth
|
|
43
|
+
|
|
44
|
+
Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes with `MultiAuth`, and get correct OpenAPI documentation by construction.
|
|
45
|
+
|
|
46
|
+
[](https://github.com/d3vyce/fastapi-multiauth/actions/workflows/ci.yml)
|
|
47
|
+
[](https://codecov.io/gh/d3vyce/fastapi-multiauth)
|
|
48
|
+
[](https://github.com/astral-sh/ty)
|
|
49
|
+
[](https://github.com/astral-sh/uv)
|
|
50
|
+
[](https://github.com/astral-sh/ruff)
|
|
51
|
+
[](https://www.python.org/downloads/)
|
|
52
|
+
[](https://opensource.org/licenses/MIT)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
**Documentation**: [https://fastapi-multiauth.d3vyce.fr](https://fastapi-multiauth.d3vyce.fr)
|
|
57
|
+
|
|
58
|
+
**Source Code**: [https://github.com/d3vyce/fastapi-multiauth](https://github.com/d3vyce/fastapi-multiauth)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
`fastapi-multiauth` is the composable layer between the raw primitives of `fastapi.security` and opinionated frameworks like fastapi-users or AuthX. You provide the validator; the library handles extraction, scheme combination, and OpenAPI declaration. No database coupling, no user model, no ready-made login routes.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv add fastapi-multiauth
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Optional extras pull in their dependencies on demand:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv add "fastapi-multiauth[jwt]" # JWTValidator (PyJWT)
|
|
74
|
+
uv add "fastapi-multiauth[oauth]" # OAuth 2.0 / OIDC helpers (httpx-oauth)
|
|
75
|
+
uv add "fastapi-multiauth[jwt,oauth]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from fastapi import FastAPI, Security
|
|
82
|
+
from fastapi_multiauth import HTTPBearerAuth, APIKeyCookieAuth, MultiAuth, UnauthorizedError
|
|
83
|
+
|
|
84
|
+
async def validate_token(token: str) -> dict:
|
|
85
|
+
user = await lookup_user_by_token(token)
|
|
86
|
+
if user is None:
|
|
87
|
+
raise UnauthorizedError()
|
|
88
|
+
return user
|
|
89
|
+
|
|
90
|
+
async def validate_session(value: str) -> dict:
|
|
91
|
+
return await lookup_user_by_session(value)
|
|
92
|
+
|
|
93
|
+
bearer = HTTPBearerAuth(validate_token, prefix="user_")
|
|
94
|
+
session = APIKeyCookieAuth("session", validate_session, secret_key="...")
|
|
95
|
+
|
|
96
|
+
# Accept either an API token or a web session on the same route.
|
|
97
|
+
auth = MultiAuth(bearer, session)
|
|
98
|
+
|
|
99
|
+
app = FastAPI()
|
|
100
|
+
|
|
101
|
+
@app.get("/me")
|
|
102
|
+
async def me(user=Security(auth)):
|
|
103
|
+
return user
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
- **BYO validator**: every source wraps a sync or async callable you provide; the library never touches your user model or database.
|
|
109
|
+
- **`MultiAuth`**: try several sources in order on a single route (e.g. web session cookie + API bearer token), with all schemes documented in OpenAPI.
|
|
110
|
+
- **Built-in sources**: covering the standard `fastapi.security` schemes:
|
|
111
|
+
- **`HTTPBearerAuth`**: bearer tokens with optional Stripe-style `user_`/`org_` prefixes to route different token types to different validators, plus `generate_token()` for secure token creation.
|
|
112
|
+
- **`APIKeyCookieAuth`**: cookie sessions with optional HMAC-SHA256 signing (via [itsdangerous](https://itsdangerous.palletsprojects.com/)), embedded expiry, and key rotation.
|
|
113
|
+
- **`APIKeyHeaderAuth`**: `X-API-Key`-style schemes, with **`APIKeyQueryAuth`** for legacy clients that can only pass a query parameter.
|
|
114
|
+
- **`HTTPBasicAuth`**: `validator(username, password)` with `WWW-Authenticate` realm support.
|
|
115
|
+
- **Token hashing helpers**: `hash_token`/`verify_token_hash` package the "store the hash, never the token" pattern with constant-time comparison.
|
|
116
|
+
- **JWT validation** (`fastapi-multiauth[jwt]` extra): `JWTValidator` for `HTTPBearerAuth`: HS256 or provider JWKS (Keycloak/Auth0/Entra/Authentik) with TTL caching and rotation-aware `kid` refresh, `aud`/`iss`/`exp` checks, configurable scope claims, and a `claims_to_identity` hook.
|
|
117
|
+
- **Security scopes**: `Security(auth, scopes=[...])` forwards the declared scopes to validators that accept a `scopes` parameter.
|
|
118
|
+
- **Correct HTTP semantics**: 401 with `WWW-Authenticate` challenges ([RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235)) and 403 via `ForbiddenError`.
|
|
119
|
+
- **OAuth 2.0 / OIDC helpers** (`fastapi-multiauth[oauth]` extra): async discovery with TTL caching, HTTPS enforcement, CSRF-protected `state` encoding, PKCE (S256), and code exchange delegated to [httpx-oauth](https://github.com/frankie567/httpx-oauth).
|
|
120
|
+
|
|
121
|
+
## Comparison
|
|
122
|
+
|
|
123
|
+
An honest map of where `fastapi-multiauth` sits. It is the composable layer between the raw primitives of `fastapi.security` and opinionated user frameworks. If you want ready-made `/register`/`/login` routes and a user model, you want a different tool.
|
|
124
|
+
|
|
125
|
+
| | `fastapi.security` (native) | **fastapi-multiauth** | [fastapi-users](https://github.com/fastapi-users/fastapi-users) | [AuthX](https://github.com/yezz123/authx) | [Authlib](https://authlib.org/) |
|
|
126
|
+
|---|---|---|---|---|---|
|
|
127
|
+
| Credential extraction + OpenAPI | ✅ | ✅ | ✅ | ✅ | ➖ |
|
|
128
|
+
| Bring-your-own validator | manual | ✅ | ❌ (own user model) | partial | manual |
|
|
129
|
+
| Multiple schemes on one route | manual | ✅ `MultiAuth` | ❌ | ❌ | ❌ |
|
|
130
|
+
| Signed cookie sessions (rotation, name-binding) | ❌ | ✅ | ✅ (own format) | ✅ (JWT in cookie) | ❌ |
|
|
131
|
+
| Opaque API tokens (prefixes, hash helpers) | ❌ | ✅ | ✅ (DB-backed) | ❌ | ❌ |
|
|
132
|
+
| JWT validation (HS + JWKS) | ❌ | ✅ `JWTValidator` | ✅ | ✅ | ✅ |
|
|
133
|
+
| JWT **issuance** / refresh | ❌ | ❌ | ✅ | ✅ | ✅ |
|
|
134
|
+
| Scope enforcement (fail-closed) | manual | ✅ | partial | ✅ | manual |
|
|
135
|
+
| OAuth login client helpers (PKCE, state) | ❌ | ✅ | ✅ (per-provider) | ❌ | ✅ (full client) |
|
|
136
|
+
| OAuth2/OIDC **server** | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
137
|
+
| User model, register/login/reset routes | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
138
|
+
| Database coupling | none | none | SQLAlchemy/Beanie | none | none |
|
|
139
|
+
| Maintenance status | active | active | maintenance mode | active | active |
|
|
140
|
+
|
|
141
|
+
When to pick what:
|
|
142
|
+
|
|
143
|
+
- **`fastapi.security` alone**: one scheme, simple validator, no sessions. The primitives are fine, this library just saves you the boilerplate around them.
|
|
144
|
+
- **fastapi-multiauth**: you own the user store and just need request-time auth: bring your own validator, combine several schemes on one route with `MultiAuth`, validate opaque tokens or JWTs, and get correct OpenAPI for free. No user model, no issued tokens, no database coupling.
|
|
145
|
+
- **fastapi-users**: you want batteries included (user table, password reset, verified-email flow) and accept its user model. Note it is in maintenance mode.
|
|
146
|
+
- **AuthX**: your session model is "login issues a JWT pair, refresh endpoint rotates it". This library validates JWTs but will never issue them.
|
|
147
|
+
- **Authlib**: you are building an OAuth2/OIDC *server*, or need a full-featured OAuth client beyond the login flow.
|
|
148
|
+
|
|
149
|
+
## FAQ
|
|
150
|
+
|
|
151
|
+
### Can I use it with sync validators?
|
|
152
|
+
|
|
153
|
+
Yes, every source accepts sync or async callables (including callable class instances) and awaits them correctly.
|
|
154
|
+
|
|
155
|
+
### Why don't my route scopes show in Swagger UI?
|
|
156
|
+
|
|
157
|
+
The OpenAPI specification only allows scope lists on `oauth2`/`openIdConnect` schemes; for `http`/`apiKey` schemes the array must be empty. Enforcement happens at runtime regardless; see [Usage → Security scopes](https://fastapi-multiauth.d3vyce.fr/usage/#security-scopes).
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# FastAPI MultiAuth
|
|
2
|
+
|
|
3
|
+
Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes with `MultiAuth`, and get correct OpenAPI documentation by construction.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/d3vyce/fastapi-multiauth/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/d3vyce/fastapi-multiauth)
|
|
7
|
+
[](https://github.com/astral-sh/ty)
|
|
8
|
+
[](https://github.com/astral-sh/uv)
|
|
9
|
+
[](https://github.com/astral-sh/ruff)
|
|
10
|
+
[](https://www.python.org/downloads/)
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
**Documentation**: [https://fastapi-multiauth.d3vyce.fr](https://fastapi-multiauth.d3vyce.fr)
|
|
16
|
+
|
|
17
|
+
**Source Code**: [https://github.com/d3vyce/fastapi-multiauth](https://github.com/d3vyce/fastapi-multiauth)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
`fastapi-multiauth` is the composable layer between the raw primitives of `fastapi.security` and opinionated frameworks like fastapi-users or AuthX. You provide the validator; the library handles extraction, scheme combination, and OpenAPI declaration. No database coupling, no user model, no ready-made login routes.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add fastapi-multiauth
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Optional extras pull in their dependencies on demand:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv add "fastapi-multiauth[jwt]" # JWTValidator (PyJWT)
|
|
33
|
+
uv add "fastapi-multiauth[oauth]" # OAuth 2.0 / OIDC helpers (httpx-oauth)
|
|
34
|
+
uv add "fastapi-multiauth[jwt,oauth]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from fastapi import FastAPI, Security
|
|
41
|
+
from fastapi_multiauth import HTTPBearerAuth, APIKeyCookieAuth, MultiAuth, UnauthorizedError
|
|
42
|
+
|
|
43
|
+
async def validate_token(token: str) -> dict:
|
|
44
|
+
user = await lookup_user_by_token(token)
|
|
45
|
+
if user is None:
|
|
46
|
+
raise UnauthorizedError()
|
|
47
|
+
return user
|
|
48
|
+
|
|
49
|
+
async def validate_session(value: str) -> dict:
|
|
50
|
+
return await lookup_user_by_session(value)
|
|
51
|
+
|
|
52
|
+
bearer = HTTPBearerAuth(validate_token, prefix="user_")
|
|
53
|
+
session = APIKeyCookieAuth("session", validate_session, secret_key="...")
|
|
54
|
+
|
|
55
|
+
# Accept either an API token or a web session on the same route.
|
|
56
|
+
auth = MultiAuth(bearer, session)
|
|
57
|
+
|
|
58
|
+
app = FastAPI()
|
|
59
|
+
|
|
60
|
+
@app.get("/me")
|
|
61
|
+
async def me(user=Security(auth)):
|
|
62
|
+
return user
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- **BYO validator**: every source wraps a sync or async callable you provide; the library never touches your user model or database.
|
|
68
|
+
- **`MultiAuth`**: try several sources in order on a single route (e.g. web session cookie + API bearer token), with all schemes documented in OpenAPI.
|
|
69
|
+
- **Built-in sources**: covering the standard `fastapi.security` schemes:
|
|
70
|
+
- **`HTTPBearerAuth`**: bearer tokens with optional Stripe-style `user_`/`org_` prefixes to route different token types to different validators, plus `generate_token()` for secure token creation.
|
|
71
|
+
- **`APIKeyCookieAuth`**: cookie sessions with optional HMAC-SHA256 signing (via [itsdangerous](https://itsdangerous.palletsprojects.com/)), embedded expiry, and key rotation.
|
|
72
|
+
- **`APIKeyHeaderAuth`**: `X-API-Key`-style schemes, with **`APIKeyQueryAuth`** for legacy clients that can only pass a query parameter.
|
|
73
|
+
- **`HTTPBasicAuth`**: `validator(username, password)` with `WWW-Authenticate` realm support.
|
|
74
|
+
- **Token hashing helpers**: `hash_token`/`verify_token_hash` package the "store the hash, never the token" pattern with constant-time comparison.
|
|
75
|
+
- **JWT validation** (`fastapi-multiauth[jwt]` extra): `JWTValidator` for `HTTPBearerAuth`: HS256 or provider JWKS (Keycloak/Auth0/Entra/Authentik) with TTL caching and rotation-aware `kid` refresh, `aud`/`iss`/`exp` checks, configurable scope claims, and a `claims_to_identity` hook.
|
|
76
|
+
- **Security scopes**: `Security(auth, scopes=[...])` forwards the declared scopes to validators that accept a `scopes` parameter.
|
|
77
|
+
- **Correct HTTP semantics**: 401 with `WWW-Authenticate` challenges ([RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235)) and 403 via `ForbiddenError`.
|
|
78
|
+
- **OAuth 2.0 / OIDC helpers** (`fastapi-multiauth[oauth]` extra): async discovery with TTL caching, HTTPS enforcement, CSRF-protected `state` encoding, PKCE (S256), and code exchange delegated to [httpx-oauth](https://github.com/frankie567/httpx-oauth).
|
|
79
|
+
|
|
80
|
+
## Comparison
|
|
81
|
+
|
|
82
|
+
An honest map of where `fastapi-multiauth` sits. It is the composable layer between the raw primitives of `fastapi.security` and opinionated user frameworks. If you want ready-made `/register`/`/login` routes and a user model, you want a different tool.
|
|
83
|
+
|
|
84
|
+
| | `fastapi.security` (native) | **fastapi-multiauth** | [fastapi-users](https://github.com/fastapi-users/fastapi-users) | [AuthX](https://github.com/yezz123/authx) | [Authlib](https://authlib.org/) |
|
|
85
|
+
|---|---|---|---|---|---|
|
|
86
|
+
| Credential extraction + OpenAPI | ✅ | ✅ | ✅ | ✅ | ➖ |
|
|
87
|
+
| Bring-your-own validator | manual | ✅ | ❌ (own user model) | partial | manual |
|
|
88
|
+
| Multiple schemes on one route | manual | ✅ `MultiAuth` | ❌ | ❌ | ❌ |
|
|
89
|
+
| Signed cookie sessions (rotation, name-binding) | ❌ | ✅ | ✅ (own format) | ✅ (JWT in cookie) | ❌ |
|
|
90
|
+
| Opaque API tokens (prefixes, hash helpers) | ❌ | ✅ | ✅ (DB-backed) | ❌ | ❌ |
|
|
91
|
+
| JWT validation (HS + JWKS) | ❌ | ✅ `JWTValidator` | ✅ | ✅ | ✅ |
|
|
92
|
+
| JWT **issuance** / refresh | ❌ | ❌ | ✅ | ✅ | ✅ |
|
|
93
|
+
| Scope enforcement (fail-closed) | manual | ✅ | partial | ✅ | manual |
|
|
94
|
+
| OAuth login client helpers (PKCE, state) | ❌ | ✅ | ✅ (per-provider) | ❌ | ✅ (full client) |
|
|
95
|
+
| OAuth2/OIDC **server** | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
96
|
+
| User model, register/login/reset routes | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
97
|
+
| Database coupling | none | none | SQLAlchemy/Beanie | none | none |
|
|
98
|
+
| Maintenance status | active | active | maintenance mode | active | active |
|
|
99
|
+
|
|
100
|
+
When to pick what:
|
|
101
|
+
|
|
102
|
+
- **`fastapi.security` alone**: one scheme, simple validator, no sessions. The primitives are fine, this library just saves you the boilerplate around them.
|
|
103
|
+
- **fastapi-multiauth**: you own the user store and just need request-time auth: bring your own validator, combine several schemes on one route with `MultiAuth`, validate opaque tokens or JWTs, and get correct OpenAPI for free. No user model, no issued tokens, no database coupling.
|
|
104
|
+
- **fastapi-users**: you want batteries included (user table, password reset, verified-email flow) and accept its user model. Note it is in maintenance mode.
|
|
105
|
+
- **AuthX**: your session model is "login issues a JWT pair, refresh endpoint rotates it". This library validates JWTs but will never issue them.
|
|
106
|
+
- **Authlib**: you are building an OAuth2/OIDC *server*, or need a full-featured OAuth client beyond the login flow.
|
|
107
|
+
|
|
108
|
+
## FAQ
|
|
109
|
+
|
|
110
|
+
### Can I use it with sync validators?
|
|
111
|
+
|
|
112
|
+
Yes, every source accepts sync or async callables (including callable class instances) and awaits them correctly.
|
|
113
|
+
|
|
114
|
+
### Why don't my route scopes show in Swagger UI?
|
|
115
|
+
|
|
116
|
+
The OpenAPI specification only allows scope lists on `oauth2`/`openIdConnect` schemes; for `http`/`apiKey` schemes the array must be empty. Enforcement happens at runtime regardless; see [Usage → Security scopes](https://fastapi-multiauth.d3vyce.fr/usage/#security-scopes).
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fastapi-multiauth"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes, correct OpenAPI by construction"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "d3vyce", email = "contact@d3vyce.fr" }
|
|
11
|
+
]
|
|
12
|
+
keywords = ["fastapi", "authentication", "security", "oauth", "api-key", "bearer", "cookie"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Framework :: AsyncIO",
|
|
16
|
+
"Framework :: FastAPI",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Internet :: WWW/HTTP :: Session",
|
|
26
|
+
"Topic :: Security",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Topic :: Software Development :: Libraries",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"anyio>=3.7",
|
|
33
|
+
"async-lru>=2.0",
|
|
34
|
+
"fastapi>=0.100.0",
|
|
35
|
+
"httpx>=0.25.0",
|
|
36
|
+
"itsdangerous>=2.1",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/d3vyce/fastapi-multiauth"
|
|
41
|
+
Documentation = "https://fastapi-multiauth.d3vyce.fr/"
|
|
42
|
+
Repository = "https://github.com/d3vyce/fastapi-multiauth"
|
|
43
|
+
Issues = "https://github.com/d3vyce/fastapi-multiauth/issues"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
jwt = [
|
|
47
|
+
"pyjwt[crypto]>=2.8",
|
|
48
|
+
]
|
|
49
|
+
oauth = [
|
|
50
|
+
"httpx-oauth>=0.16",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
{include-group = "tests"},
|
|
56
|
+
{include-group = "docs"},
|
|
57
|
+
"fastapi_multiauth[jwt,oauth]",
|
|
58
|
+
"prek>=0.3.8",
|
|
59
|
+
"ruff>=0.1.0",
|
|
60
|
+
"ty>=0.0.34",
|
|
61
|
+
]
|
|
62
|
+
tests = [
|
|
63
|
+
"coverage>=7.0.0",
|
|
64
|
+
"pytest-anyio>=0.0.0",
|
|
65
|
+
"pytest-cov>=4.0.0",
|
|
66
|
+
"pytest>=8.0.0",
|
|
67
|
+
"respx>=0.22",
|
|
68
|
+
]
|
|
69
|
+
docs = [
|
|
70
|
+
"mkdocstrings-python>=2.0.2",
|
|
71
|
+
"zensical>=0.0.30",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[build-system]
|
|
75
|
+
requires = ["uv_build>=0.11.8,<0.12.0"]
|
|
76
|
+
build-backend = "uv_build"
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
# "S" = flake8-bandit security rules, scoped to the library source.
|
|
80
|
+
extend-select = ["S"]
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint.per-file-ignores]
|
|
83
|
+
"tests/**" = ["S"]
|
|
84
|
+
|
|
85
|
+
[tool.pytest.ini_options]
|
|
86
|
+
testpaths = ["tests"]
|
|
87
|
+
anyio_mode = "auto"
|
|
88
|
+
filterwarnings = [
|
|
89
|
+
"ignore::DeprecationWarning",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.coverage.run]
|
|
93
|
+
source = ["src/fastapi_multiauth"]
|
|
94
|
+
branch = true
|
|
95
|
+
|
|
96
|
+
[tool.coverage.report]
|
|
97
|
+
exclude_lines = [
|
|
98
|
+
"pragma: no cover",
|
|
99
|
+
"if TYPE_CHECKING:",
|
|
100
|
+
"raise NotImplementedError",
|
|
101
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Composable authentication sources for FastAPI using Security().
|
|
2
|
+
|
|
3
|
+
The OAuth 2.0 / OIDC login-flow helpers live in the :mod:`fastapi_multiauth.oauth`
|
|
4
|
+
namespace — they are a distinct concern from the request-time auth sources
|
|
5
|
+
exported here, and require the ``oauth`` extra. Import them explicitly::
|
|
6
|
+
|
|
7
|
+
from fastapi_multiauth import oauth
|
|
8
|
+
|
|
9
|
+
redirect = oauth.oauth_build_authorization_redirect(...)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from . import oauth
|
|
13
|
+
from .abc import AuthSource
|
|
14
|
+
from .exceptions import ForbiddenError, UnauthorizedError
|
|
15
|
+
from .jwt import JWTValidator
|
|
16
|
+
from .sources import (
|
|
17
|
+
APIKeyCookieAuth,
|
|
18
|
+
APIKeyHeaderAuth,
|
|
19
|
+
APIKeyQueryAuth,
|
|
20
|
+
HTTPBasicAuth,
|
|
21
|
+
HTTPBearerAuth,
|
|
22
|
+
MultiAuth,
|
|
23
|
+
)
|
|
24
|
+
from .utils import hash_token, verify_token_hash
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"APIKeyCookieAuth",
|
|
30
|
+
"APIKeyHeaderAuth",
|
|
31
|
+
"APIKeyQueryAuth",
|
|
32
|
+
"AuthSource",
|
|
33
|
+
"ForbiddenError",
|
|
34
|
+
"HTTPBasicAuth",
|
|
35
|
+
"HTTPBearerAuth",
|
|
36
|
+
"JWTValidator",
|
|
37
|
+
"MultiAuth",
|
|
38
|
+
"UnauthorizedError",
|
|
39
|
+
"hash_token",
|
|
40
|
+
"oauth",
|
|
41
|
+
"verify_token_hash",
|
|
42
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Shared HTTP transport hygiene for the OAuth and JWT modules."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlsplit
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
_LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1"})
|
|
10
|
+
|
|
11
|
+
DEFAULT_TIMEOUT = 10.0
|
|
12
|
+
"""Default timeout (seconds) applied to every HTTP call made by this library."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _require_https(url: str, description: str) -> str:
|
|
16
|
+
"""Reject non-string URLs and URLs that would send credentials over plaintext HTTP."""
|
|
17
|
+
if not isinstance(url, str):
|
|
18
|
+
raise ValueError(f"{description} must be a string URL (got {url!r})")
|
|
19
|
+
parsed = urlsplit(url)
|
|
20
|
+
if parsed.scheme == "https":
|
|
21
|
+
return url
|
|
22
|
+
if parsed.scheme == "http" and parsed.hostname in _LOOPBACK_HOSTS:
|
|
23
|
+
return url
|
|
24
|
+
raise ValueError(f"{description} must use https:// (got {url!r})")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _get_json(
|
|
28
|
+
url: str,
|
|
29
|
+
*,
|
|
30
|
+
timeout: float,
|
|
31
|
+
error_cls: type[Exception],
|
|
32
|
+
description: str,
|
|
33
|
+
headers: dict[str, str] | None = None,
|
|
34
|
+
) -> Any:
|
|
35
|
+
"""GET *url* and decode the JSON body, mapping failures to *error_cls*."""
|
|
36
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
37
|
+
try:
|
|
38
|
+
resp = await client.get(url, headers=headers)
|
|
39
|
+
resp.raise_for_status()
|
|
40
|
+
return resp.json()
|
|
41
|
+
except httpx.HTTPError as exc:
|
|
42
|
+
raise error_cls(f"failed to fetch {description}: {exc}") from exc
|
|
43
|
+
except json.JSONDecodeError as exc:
|
|
44
|
+
raise error_cls(f"{description} is not valid JSON") from exc
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Optional dependency helpers."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def require_extra(package: str, extra: str) -> None:
|
|
5
|
+
"""Raise *ImportError* with an actionable install instruction."""
|
|
6
|
+
raise ImportError(
|
|
7
|
+
f"'{package}' is required to use this feature. "
|
|
8
|
+
f"Install it with: pip install fastapi-multiauth[{extra}]"
|
|
9
|
+
)
|