fastapi-auth-manager-dep 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.
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2026 Edmundo Andrade
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ and associated documentation files (the “Software”), to deal in the Software without
7
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or
12
+ substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
18
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ exclude src/fastapi_auth/example_app.py
2
+ recursive-exclude src/fastapi_auth/tests *
3
+ recursive-exclude src/fastapi_auth/__pycache__ *
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-auth-manager-dep
3
+ Version: 0.1.0
4
+ Summary: Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support.
5
+ Author-email: Edmundo Andrade <edmon.af@gmail.com>
6
+ Project-URL: Homepage, https://github.com/edmon1024/fastapi-auth-manager-dep
7
+ Project-URL: Documentation, https://github.com/edmon1024/fastapi-auth-manager-dep
8
+ Project-URL: Repository, https://github.com/edmon1024/fastapi-auth-manager-dep
9
+ Project-URL: Issues, https://github.com/edmon1024/fastapi-auth-manager-dep/issues
10
+ Project-URL: Changelog, https://github.com/edmon1024/fastapi-auth-manager-dep/CHANGELOG.md
11
+ Keywords: fastapi,fastapi-auth-middleware,fastapi-jwt-auth,fastapi-jwt
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Internet
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: fastapi>=0.100.0
30
+ Requires-Dist: pydantic>=2.0.0
31
+ Requires-Dist: pydantic-settings>=2.0.0
32
+ Requires-Dist: pyjwt>=2.4.0
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest>=7.0.0; extra == "test"
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
36
+ Requires-Dist: httpx>=0.24.0; extra == "test"
37
+ Dynamic: license-file
38
+
39
+ # fastapi-auth-manager-dep
40
+
41
+ Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support.
42
+
43
+ - One mandatory ADMIN key via envvar (super-key, always valid)
44
+ - Additional api-keys with labels/roles via JSON envvar
45
+ - Fine-grained per-endpoint control: which roles each endpoint accepts
46
+ - HMAC JWT with configurable algorithms
47
+ - Public endpoints via explicit opt-out
48
+
49
+ ---
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install fastapi-auth-manager-dep
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Environment variables
60
+
61
+ | Variable | Required | Description |
62
+ |-------------------|----------|-------------------------------------------------------------------------------|
63
+ | `AUTH_ADMIN_API_KEY` | Yes | Administrator api-key. Super-key: valid on every endpoint. |
64
+ | `AUTH_API_KEYS` | No | JSON object mapping api-keys to their labels. See format below. |
65
+ | `AUTH_JWT_SECRET_KEY`| No* | HMAC secret key for verifying user JWTs. Required when using `"jwt"`. |
66
+ | `AUTH_JWT_ALGORITHMS` | No | List of allowed algorithms (default: `["HS256", "HS384", "HS512"]`). |
67
+
68
+ ### `AUTH_API_KEYS` format
69
+
70
+ A JSON object where each key is the raw api-key value and the value is its label/role:
71
+
72
+ ```bash
73
+ AUTH_API_KEYS='{"key-abc123": "reports", "key-xyz789": "billing", "key-qrs456": "billing"}'
74
+ ```
75
+
76
+ - Multiple keys can share the same label (same role).
77
+ - Empty string (`AUTH_API_KEYS=""`) is equivalent to having no additional keys.
78
+
79
+ ### `.env` example
80
+
81
+ ```dotenv
82
+ AUTH_ADMIN_API_KEY=super-secret-admin-key
83
+ AUTH_API_KEYS={"key-reports-1": "reports", "key-billing-1": "billing", "key-billing-2": "billing"}
84
+ AUTH_JWT_SECRET_KEY=my-jwt-secret-key
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Usage
90
+
91
+ ### 1. Global authentication (entire app)
92
+
93
+ All endpoints are protected with the ADMIN key by default:
94
+
95
+ ```python
96
+ from fastapi import Depends, FastAPI
97
+ from fastapi_auth import AuthDependency
98
+
99
+ app = FastAPI(dependencies=[Depends(AuthDependency())])
100
+ ```
101
+
102
+ ### 2. Restrict by api-key label
103
+
104
+ Only the ADMIN key and keys labelled `"reports"` can access:
105
+
106
+ ```python
107
+ from fastapi_auth import AuthDependency
108
+
109
+ @app.get(
110
+ "/reports",
111
+ dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))]
112
+ )
113
+ async def get_reports():
114
+ ...
115
+ ```
116
+
117
+ ### 3. Combine api-key and JWT on the same endpoint
118
+
119
+ ```python
120
+ from fastapi_auth import AuthDependency
121
+
122
+ @app.get(
123
+ "/billing",
124
+ dependencies=[Depends(AuthDependency(valid_token_types={"billing", "jwt"}))]
125
+ )
126
+ async def get_billing():
127
+ ...
128
+ ```
129
+
130
+ ### 4. Allow all additional api-keys
131
+
132
+ ```python
133
+ from fastapi_auth import AuthDependency
134
+
135
+ # Using the AuthDependency.ALL sentinel
136
+ @app.get(
137
+ "/any",
138
+ dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))]
139
+ )
140
+ async def any_key_endpoint():
141
+ ...
142
+
143
+ # Equivalent using a string
144
+ AuthDependency(valid_token_types="*")
145
+ ```
146
+
147
+ ### 5. Access the authenticated principal inside a handler
148
+
149
+ `AuthDependency` returns an `AuthPrincipal` with the method used, role, and JWT payload:
150
+
151
+ ```python
152
+ from fastapi_auth import AuthDependency, AuthPrincipal
153
+
154
+ @app.get("/me")
155
+ async def me(
156
+ principal: AuthPrincipal = Depends(
157
+ AuthDependency(valid_token_types={"jwt", "billing"})
158
+ )
159
+ ):
160
+ return {
161
+ "method": principal.method, # "jwt" | "api_key"
162
+ "sub": principal.sub, # user_id (JWT) or raw key value (api-key)
163
+ "role": principal.role, # "admin" | "reports" | "billing" | None (JWT)
164
+ "payload": principal.payload, # full JWT dict | None
165
+ }
166
+ ```
167
+
168
+ ### 6. Public endpoint (opt-out of global auth)
169
+
170
+ When auth is configured globally, use `PublicRoute` to exclude specific endpoints:
171
+
172
+ ```python
173
+ from fastapi_auth import PublicRoute
174
+
175
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
176
+ async def health():
177
+ return {"status": "ok"}
178
+ ```
179
+
180
+ ---
181
+
182
+ ## `valid_token_types` behaviour reference
183
+
184
+ | `valid_token_types` | ADMIN key | Additional keys | User JWT |
185
+ |------------------------------|-----------|-------------------------|----------|
186
+ | `None` (default) | Yes | No | No |
187
+ | `{"reports"}` | Yes | `reports` label only | No |
188
+ | `{"billing", "jwt"}` | Yes | `billing` label only | Yes |
189
+ | `AuthDependency.ALL` / `"*"` | Yes | All | No |
190
+ | `{"jwt"}` | Yes | No | Yes |
191
+
192
+ > The ADMIN key is always valid regardless of the endpoint configuration.
193
+
194
+ ---
195
+
196
+ ## `AuthPrincipal` — return object
197
+
198
+ ```python
199
+ class AuthPrincipal(BaseModel):
200
+ method: AuthMethod # AuthMethod.JWT | AuthMethod.AUTH_ADMIN_API_KEY
201
+ sub: str # user_id (JWT) or raw api-key value
202
+ role: str | None = None # key role/label; None for JWT
203
+ payload: dict | None = None # full JWT payload; None for api-key
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Error responses
209
+
210
+ All authentication errors return HTTP `401 Unauthorized` with a `detail` field:
211
+
212
+ | Situation | `detail` |
213
+ |--------------------------------------|-------------------------------------------|
214
+ | No credentials provided | `"Authentication credentials are required"` |
215
+ | Api-key not found or not allowed | `"Invalid authentication credentials"` |
216
+ | Expired JWT | `"JWT token has expired"` |
217
+ | Invalid JWT signature | `"Invalid JWT token: ..."` |
218
+ | Disallowed JWT algorithm | `"JWT algorithm not allowed: RS256"` |
219
+
220
+ ---
221
+
222
+ ## Tests
223
+
224
+ ```bash
225
+ pip install pytest pytest-asyncio
226
+ pytest tests/ -v
227
+ ```
228
+
229
+ Test coverage includes:
230
+
231
+ - ADMIN key as super-key across endpoints with different restrictions
232
+ - Label-based restriction: accepts the correct label, rejects others
233
+ - Multiple keys sharing the same label
234
+ - `ALL` sentinel and its `"*"` string equivalent
235
+ - Valid JWT, expired JWT, JWT ignored when `"jwt"` is not enabled
236
+ - No credentials
237
+ - `AUTH_API_KEYS` validation in settings (JSON string, dict, invalid JSON)
238
+
239
+ ---
240
+
241
+ ## Full example
242
+
243
+ ```python
244
+ from fastapi import Depends, FastAPI
245
+ from fastapi_auth import AuthDependency, AuthPrincipal, PublicRoute
246
+
247
+ app = FastAPI(dependencies=[Depends(AuthDependency())]) # global: ADMIN key only
248
+
249
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
250
+ async def health():
251
+ return {"status": "ok"}
252
+
253
+ @app.get("/reports", dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))])
254
+ async def reports():
255
+ return {"data": "..."}
256
+
257
+ @app.get("/me")
258
+ async def me(
259
+ principal: AuthPrincipal = Depends(AuthDependency(valid_token_types={"jwt"}))
260
+ ):
261
+ return {"sub": principal.sub, "payload": principal.payload}
262
+
263
+ @app.get("/internal", dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))])
264
+ async def internal():
265
+ return {"message": "any valid api-key accepted"}
266
+ ```
@@ -0,0 +1,228 @@
1
+ # fastapi-auth-manager-dep
2
+
3
+ Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support.
4
+
5
+ - One mandatory ADMIN key via envvar (super-key, always valid)
6
+ - Additional api-keys with labels/roles via JSON envvar
7
+ - Fine-grained per-endpoint control: which roles each endpoint accepts
8
+ - HMAC JWT with configurable algorithms
9
+ - Public endpoints via explicit opt-out
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install fastapi-auth-manager-dep
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Environment variables
22
+
23
+ | Variable | Required | Description |
24
+ |-------------------|----------|-------------------------------------------------------------------------------|
25
+ | `AUTH_ADMIN_API_KEY` | Yes | Administrator api-key. Super-key: valid on every endpoint. |
26
+ | `AUTH_API_KEYS` | No | JSON object mapping api-keys to their labels. See format below. |
27
+ | `AUTH_JWT_SECRET_KEY`| No* | HMAC secret key for verifying user JWTs. Required when using `"jwt"`. |
28
+ | `AUTH_JWT_ALGORITHMS` | No | List of allowed algorithms (default: `["HS256", "HS384", "HS512"]`). |
29
+
30
+ ### `AUTH_API_KEYS` format
31
+
32
+ A JSON object where each key is the raw api-key value and the value is its label/role:
33
+
34
+ ```bash
35
+ AUTH_API_KEYS='{"key-abc123": "reports", "key-xyz789": "billing", "key-qrs456": "billing"}'
36
+ ```
37
+
38
+ - Multiple keys can share the same label (same role).
39
+ - Empty string (`AUTH_API_KEYS=""`) is equivalent to having no additional keys.
40
+
41
+ ### `.env` example
42
+
43
+ ```dotenv
44
+ AUTH_ADMIN_API_KEY=super-secret-admin-key
45
+ AUTH_API_KEYS={"key-reports-1": "reports", "key-billing-1": "billing", "key-billing-2": "billing"}
46
+ AUTH_JWT_SECRET_KEY=my-jwt-secret-key
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Usage
52
+
53
+ ### 1. Global authentication (entire app)
54
+
55
+ All endpoints are protected with the ADMIN key by default:
56
+
57
+ ```python
58
+ from fastapi import Depends, FastAPI
59
+ from fastapi_auth import AuthDependency
60
+
61
+ app = FastAPI(dependencies=[Depends(AuthDependency())])
62
+ ```
63
+
64
+ ### 2. Restrict by api-key label
65
+
66
+ Only the ADMIN key and keys labelled `"reports"` can access:
67
+
68
+ ```python
69
+ from fastapi_auth import AuthDependency
70
+
71
+ @app.get(
72
+ "/reports",
73
+ dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))]
74
+ )
75
+ async def get_reports():
76
+ ...
77
+ ```
78
+
79
+ ### 3. Combine api-key and JWT on the same endpoint
80
+
81
+ ```python
82
+ from fastapi_auth import AuthDependency
83
+
84
+ @app.get(
85
+ "/billing",
86
+ dependencies=[Depends(AuthDependency(valid_token_types={"billing", "jwt"}))]
87
+ )
88
+ async def get_billing():
89
+ ...
90
+ ```
91
+
92
+ ### 4. Allow all additional api-keys
93
+
94
+ ```python
95
+ from fastapi_auth import AuthDependency
96
+
97
+ # Using the AuthDependency.ALL sentinel
98
+ @app.get(
99
+ "/any",
100
+ dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))]
101
+ )
102
+ async def any_key_endpoint():
103
+ ...
104
+
105
+ # Equivalent using a string
106
+ AuthDependency(valid_token_types="*")
107
+ ```
108
+
109
+ ### 5. Access the authenticated principal inside a handler
110
+
111
+ `AuthDependency` returns an `AuthPrincipal` with the method used, role, and JWT payload:
112
+
113
+ ```python
114
+ from fastapi_auth import AuthDependency, AuthPrincipal
115
+
116
+ @app.get("/me")
117
+ async def me(
118
+ principal: AuthPrincipal = Depends(
119
+ AuthDependency(valid_token_types={"jwt", "billing"})
120
+ )
121
+ ):
122
+ return {
123
+ "method": principal.method, # "jwt" | "api_key"
124
+ "sub": principal.sub, # user_id (JWT) or raw key value (api-key)
125
+ "role": principal.role, # "admin" | "reports" | "billing" | None (JWT)
126
+ "payload": principal.payload, # full JWT dict | None
127
+ }
128
+ ```
129
+
130
+ ### 6. Public endpoint (opt-out of global auth)
131
+
132
+ When auth is configured globally, use `PublicRoute` to exclude specific endpoints:
133
+
134
+ ```python
135
+ from fastapi_auth import PublicRoute
136
+
137
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
138
+ async def health():
139
+ return {"status": "ok"}
140
+ ```
141
+
142
+ ---
143
+
144
+ ## `valid_token_types` behaviour reference
145
+
146
+ | `valid_token_types` | ADMIN key | Additional keys | User JWT |
147
+ |------------------------------|-----------|-------------------------|----------|
148
+ | `None` (default) | Yes | No | No |
149
+ | `{"reports"}` | Yes | `reports` label only | No |
150
+ | `{"billing", "jwt"}` | Yes | `billing` label only | Yes |
151
+ | `AuthDependency.ALL` / `"*"` | Yes | All | No |
152
+ | `{"jwt"}` | Yes | No | Yes |
153
+
154
+ > The ADMIN key is always valid regardless of the endpoint configuration.
155
+
156
+ ---
157
+
158
+ ## `AuthPrincipal` — return object
159
+
160
+ ```python
161
+ class AuthPrincipal(BaseModel):
162
+ method: AuthMethod # AuthMethod.JWT | AuthMethod.AUTH_ADMIN_API_KEY
163
+ sub: str # user_id (JWT) or raw api-key value
164
+ role: str | None = None # key role/label; None for JWT
165
+ payload: dict | None = None # full JWT payload; None for api-key
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Error responses
171
+
172
+ All authentication errors return HTTP `401 Unauthorized` with a `detail` field:
173
+
174
+ | Situation | `detail` |
175
+ |--------------------------------------|-------------------------------------------|
176
+ | No credentials provided | `"Authentication credentials are required"` |
177
+ | Api-key not found or not allowed | `"Invalid authentication credentials"` |
178
+ | Expired JWT | `"JWT token has expired"` |
179
+ | Invalid JWT signature | `"Invalid JWT token: ..."` |
180
+ | Disallowed JWT algorithm | `"JWT algorithm not allowed: RS256"` |
181
+
182
+ ---
183
+
184
+ ## Tests
185
+
186
+ ```bash
187
+ pip install pytest pytest-asyncio
188
+ pytest tests/ -v
189
+ ```
190
+
191
+ Test coverage includes:
192
+
193
+ - ADMIN key as super-key across endpoints with different restrictions
194
+ - Label-based restriction: accepts the correct label, rejects others
195
+ - Multiple keys sharing the same label
196
+ - `ALL` sentinel and its `"*"` string equivalent
197
+ - Valid JWT, expired JWT, JWT ignored when `"jwt"` is not enabled
198
+ - No credentials
199
+ - `AUTH_API_KEYS` validation in settings (JSON string, dict, invalid JSON)
200
+
201
+ ---
202
+
203
+ ## Full example
204
+
205
+ ```python
206
+ from fastapi import Depends, FastAPI
207
+ from fastapi_auth import AuthDependency, AuthPrincipal, PublicRoute
208
+
209
+ app = FastAPI(dependencies=[Depends(AuthDependency())]) # global: ADMIN key only
210
+
211
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
212
+ async def health():
213
+ return {"status": "ok"}
214
+
215
+ @app.get("/reports", dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))])
216
+ async def reports():
217
+ return {"data": "..."}
218
+
219
+ @app.get("/me")
220
+ async def me(
221
+ principal: AuthPrincipal = Depends(AuthDependency(valid_token_types={"jwt"}))
222
+ ):
223
+ return {"sub": principal.sub, "payload": principal.payload}
224
+
225
+ @app.get("/internal", dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))])
226
+ async def internal():
227
+ return {"message": "any valid api-key accepted"}
228
+ ```
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=82.0.1", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fastapi-auth-manager-dep"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Edmundo Andrade", email="edmon.af@gmail.com" },
10
+ ]
11
+ description = "Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support."
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ keywords = [
15
+ "fastapi",
16
+ "fastapi-auth-middleware",
17
+ "fastapi-jwt-auth",
18
+ "fastapi-jwt",
19
+ ]
20
+ classifiers = [
21
+ "Framework :: FastAPI",
22
+ "Operating System :: OS Independent",
23
+ "Topic :: Internet",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Topic :: Software Development :: Libraries",
26
+ "Topic :: Software Development",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Programming Language :: Python :: 3.14",
35
+ ]
36
+ dependencies = [
37
+ "fastapi>=0.100.0",
38
+ "pydantic>=2.0.0",
39
+ "pydantic-settings>=2.0.0",
40
+ "pyjwt>=2.4.0",
41
+ ]
42
+ [project.optional-dependencies]
43
+ test = [
44
+ "pytest>=7.0.0",
45
+ "pytest-asyncio>=0.21.0",
46
+ "httpx>=0.24.0",
47
+ ]
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools]
52
+ exclude-package-data = {"fastapi_auth" = ["tests*", "example_app.py"]}
53
+
54
+ [project.urls]
55
+ Homepage = "https://github.com/edmon1024/fastapi-auth-manager-dep"
56
+ Documentation = "https://github.com/edmon1024/fastapi-auth-manager-dep"
57
+ Repository = "https://github.com/edmon1024/fastapi-auth-manager-dep"
58
+ Issues = "https://github.com/edmon1024/fastapi-auth-manager-dep/issues"
59
+ Changelog = "https://github.com/edmon1024/fastapi-auth-manager-dep/CHANGELOG.md"
60
+
61
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from .dependency import AuthDependency
2
+ from .enums import AuthMethod
3
+ from .public import PublicRoute
4
+ from .schemas import AuthPrincipal
5
+
6
+ __all__ = [
7
+ "AuthDependency",
8
+ "AuthMethod",
9
+ "PublicRoute",
10
+ "AuthPrincipal",
11
+ ]
@@ -0,0 +1,253 @@
1
+ import logging
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ import jwt as pyjwt
6
+ from fastapi import HTTPException, Security, status
7
+ from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
8
+
9
+ from .enums import AuthMethod
10
+ from .schemas import AuthPrincipal
11
+ from .settings import AuthSettings, get_auth_settings
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Security schemes shared across all instances (module-level singletons)
16
+ _bearer_scheme = HTTPBearer(auto_error=False)
17
+ _api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
18
+
19
+ # Sentinel value for "allow all additional api-keys"
20
+ _ALL = object()
21
+
22
+
23
+ class AuthDependency:
24
+ """
25
+ Authentication dependency for FastAPI.
26
+
27
+ Supported mechanisms
28
+ --------------------
29
+ - **API Key** via ``X-API-Key`` header
30
+ - **JWT Bearer** via ``Authorization: Bearer <token>`` header
31
+ (only when ``"jwt"`` is included in ``valid_token_types``)
32
+
33
+ Api-key logic
34
+ -------------
35
+ The ADMIN key (``AUTH_ADMIN_API_KEY`` envvar) is a **super-key**: always valid on any
36
+ endpoint, regardless of ``valid_token_types``.
37
+
38
+ Additional keys are defined in the ``AUTH_API_KEYS`` envvar as JSON::
39
+
40
+ AUTH_API_KEYS='{"key-abc": "reports", "key-xyz": "billing"}'
41
+
42
+ Each key's label must be used for per-endpoint restrictions via ``valid_token_types``.
43
+
44
+ Controlling which keys a given instance accepts
45
+ -----------------------------------------------
46
+ ``valid_token_types=None``
47
+ ADMIN key only (default behaviour).
48
+
49
+ ``valid_token_types={"reports"}``
50
+ ADMIN key + all keys labelled ``"reports"``.
51
+
52
+ ``valid_token_types="*"`` or ``valid_token_types=AuthDependency.ALL``
53
+ ADMIN key + **all** additional keys with no label filter.
54
+
55
+ Quick reference
56
+ ---------------
57
+ ::
58
+
59
+ # Global: ADMIN key only
60
+ app = FastAPI(dependencies=[Depends(AuthDependency())])
61
+
62
+ # Endpoint: accept "reports" keys and user JWTs
63
+ @router.get("/report", dependencies=[Depends(
64
+ AuthDependency(valid_token_types={"reports", "jwt"})
65
+ )])
66
+
67
+ # Endpoint: accept all additional keys
68
+ @router.get("/open", dependencies=[Depends(
69
+ AuthDependency(valid_token_types=AuthDependency.ALL)
70
+ )])
71
+
72
+ # Access the authenticated principal inside the handler
73
+ @router.get("/me")
74
+ async def me(principal: AuthPrincipal = Depends(AuthDependency(...))):
75
+ return principal
76
+ """
77
+
78
+ #: Sentinel to allow all additional api-keys without label filtering.
79
+ ALL = _ALL
80
+
81
+ def __init__(
82
+ self,
83
+ valid_token_types: "set[str | Enum] | object | None" = None,
84
+ settings: AuthSettings | None = None,
85
+ ) -> None:
86
+ """
87
+ Parameters
88
+ ----------
89
+ valid_token_types:
90
+ - ``None`` → ADMIN key only.
91
+ - ``{"role-a"}`` → ADMIN key + keys labelled ``"role-a"``.
92
+ - ``AuthDependency.ALL`` / ``"*"`` → ADMIN key + all additional keys.
93
+ - Include ``"jwt"`` to enable user JWT authentication.
94
+ settings:
95
+ Configuration injection. Defaults to ``get_auth_settings()``.
96
+ """
97
+ self._settings = settings or get_auth_settings()
98
+ self._allow_all_keys: bool = valid_token_types is _ALL or valid_token_types == "*"
99
+
100
+ if self._allow_all_keys:
101
+ self._valid_token_types: set[str] = set()
102
+ else:
103
+ self._valid_token_types = self._normalize(valid_token_types)
104
+
105
+ # ------------------------------------------------------------------
106
+ # Static helpers
107
+ # ------------------------------------------------------------------
108
+
109
+ @staticmethod
110
+ def _normalize(raw: "set[str | Enum] | None") -> set[str]:
111
+ """Converts a mixed Enum/str set to a plain set of strings."""
112
+ if not raw:
113
+ return set()
114
+ result: set[str] = set()
115
+ for item in raw:
116
+ result.add(item.value if isinstance(item, Enum) else str(item))
117
+ return result
118
+
119
+ # ------------------------------------------------------------------
120
+ # Allowed api-key set construction
121
+ # ------------------------------------------------------------------
122
+
123
+ def _allowed_api_keys(self) -> set[str]:
124
+ """
125
+ Returns the set of valid api-keys for this instance.
126
+
127
+ Rules:
128
+ 1. ADMIN key is always included (super-key).
129
+ 2. If ``_allow_all_keys`` → every key from ``AUTH_API_KEYS``.
130
+ 3. Otherwise → only keys whose label is in ``_valid_token_types``.
131
+ """
132
+ allowed: set[str] = set()
133
+
134
+ # 1. ADMIN super-key (always)
135
+ if self._settings.AUTH_ADMIN_API_KEY:
136
+ allowed.add(self._settings.AUTH_ADMIN_API_KEY)
137
+
138
+ # 2. Additional keys according to policy
139
+ if self._allow_all_keys:
140
+ allowed.update(k for k in self._settings.AUTH_API_KEYS if k)
141
+ else:
142
+ for label in self._valid_token_types:
143
+ if label == "jwt":
144
+ continue # label is "jwt", which indicates JWT auth, not an api-key
145
+ allowed.update(self._settings.keys_for_label(label))
146
+
147
+ return allowed
148
+
149
+ # ------------------------------------------------------------------
150
+ # JWT verification
151
+ # ------------------------------------------------------------------
152
+
153
+ def _verify_jwt(self, token: str) -> dict:
154
+ """
155
+ Decodes and validates an HMAC JWT.
156
+
157
+ Raises
158
+ ------
159
+ HTTPException 401 for invalid, expired, or disallowed-algorithm tokens.
160
+ """
161
+ try:
162
+ header = pyjwt.get_unverified_header(token)
163
+ alg = header.get("alg")
164
+ if alg not in self._settings.AUTH_JWT_ALGORITHMS:
165
+ raise HTTPException(
166
+ status_code=status.HTTP_401_UNAUTHORIZED,
167
+ detail=f"JWT algorithm not allowed: {alg}",
168
+ )
169
+ payload = pyjwt.decode(
170
+ token,
171
+ self._settings.AUTH_JWT_SECRET_KEY,
172
+ algorithms=self._settings.AUTH_JWT_ALGORITHMS,
173
+ )
174
+ return payload
175
+ except HTTPException:
176
+ raise
177
+ except pyjwt.ExpiredSignatureError:
178
+ raise HTTPException(
179
+ status_code=status.HTTP_401_UNAUTHORIZED,
180
+ detail="JWT token has expired",
181
+ )
182
+ except pyjwt.InvalidTokenError as exc:
183
+ raise HTTPException(
184
+ status_code=status.HTTP_401_UNAUTHORIZED,
185
+ detail=f"Invalid JWT token: {exc}",
186
+ )
187
+
188
+ # ------------------------------------------------------------------
189
+ # Api-key verification
190
+ # ------------------------------------------------------------------
191
+
192
+ def _verify_api_key(self, api_key: str) -> str:
193
+ """
194
+ Validates the api-key against the allowed set.
195
+
196
+ Returns
197
+ -------
198
+ The label/role associated with the key (``"admin"`` for the ADMIN key).
199
+
200
+ Raises
201
+ ------
202
+ HTTPException 401 if the key is not in the allowed set.
203
+ """
204
+ if api_key not in self._allowed_api_keys():
205
+ raise HTTPException(
206
+ status_code=status.HTTP_401_UNAUTHORIZED,
207
+ detail="Invalid authentication credentials",
208
+ )
209
+ return self._settings.label_for_key(api_key) or "unknown"
210
+
211
+ # ------------------------------------------------------------------
212
+ # Main callable
213
+ # ------------------------------------------------------------------
214
+
215
+ async def __call__(
216
+ self,
217
+ credentials: Optional[HTTPAuthorizationCredentials] = Security(_bearer_scheme),
218
+ api_key: Optional[str] = Security(_api_key_header),
219
+ ) -> AuthPrincipal:
220
+ """
221
+ Evaluation order:
222
+ 1. JWT Bearer → if ``"jwt"`` is enabled and a Bearer token is present.
223
+ 2. API Key → if the ``X-API-Key`` header is present.
224
+ 3. 401 → no valid mechanism found.
225
+
226
+ Returns
227
+ -------
228
+ :class:`AuthPrincipal` with method, sub, role, and optional payload.
229
+ """
230
+ # 1. JWT
231
+ jwt_enabled = "jwt" in self._valid_token_types
232
+ if jwt_enabled and credentials and credentials.scheme.lower() == "bearer":
233
+ payload = self._verify_jwt(credentials.credentials)
234
+ return AuthPrincipal(
235
+ method=AuthMethod.JWT,
236
+ sub=payload.get("sub", ""),
237
+ payload=payload,
238
+ )
239
+
240
+ # 2. API Key
241
+ if api_key:
242
+ label = self._verify_api_key(api_key)
243
+ return AuthPrincipal(
244
+ method=AuthMethod.AUTH_ADMIN_API_KEY,
245
+ sub=api_key,
246
+ role=label,
247
+ )
248
+
249
+ # 3. No credentials → 401
250
+ raise HTTPException(
251
+ status_code=status.HTTP_401_UNAUTHORIZED,
252
+ detail="Authentication credentials are required",
253
+ )
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AuthMethod(str, Enum):
5
+ JWT = "jwt"
6
+ AUTH_ADMIN_API_KEY = "api_key"
@@ -0,0 +1,36 @@
1
+ from typing import Optional
2
+
3
+ from fastapi import Security
4
+ from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
5
+
6
+ from .schemas import AuthPrincipal
7
+
8
+ _bearer_scheme = HTTPBearer(auto_error=False)
9
+ _api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
10
+
11
+
12
+ class PublicRoute:
13
+ """
14
+ No-auth dependency for public endpoints.
15
+
16
+ When authentication is configured globally on the app
17
+ (via ``app = FastAPI(dependencies=[Depends(AuthDependency())])``),
18
+ use this dependency to opt specific endpoints out of authentication.
19
+ It accepts any request without validation.
20
+
21
+ Usage
22
+ -----
23
+ ```python
24
+ @router.get("/health", dependencies=[Depends(PublicRoute())])
25
+ async def health():
26
+ return {"status": "ok"}
27
+ ```
28
+ """
29
+
30
+ async def __call__(
31
+ self,
32
+ credentials: Optional[HTTPAuthorizationCredentials] = Security(_bearer_scheme),
33
+ api_key: Optional[str] = Security(_api_key_header),
34
+ ) -> None:
35
+ """Always returns None without validating anything."""
36
+ return None
@@ -0,0 +1,23 @@
1
+ from typing import Any, Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from .enums import AuthMethod
6
+
7
+
8
+ class AuthPrincipal(BaseModel):
9
+ """
10
+ Authentication result returned by AuthDependency.
11
+
12
+ Attributes
13
+ ----------
14
+ method : Mechanism used (``"jwt"`` or ``"api_key"``).
15
+ sub : Subject identifier (``user_id`` for JWT, raw key value for api-key).
16
+ role : Api-key label/role (``"admin"``, ``"reports"``…). None for JWT.
17
+ payload : Full JWT payload. None for api-key.
18
+ """
19
+
20
+ method: AuthMethod
21
+ sub: str
22
+ role: Optional[str] = None
23
+ payload: Optional[dict[str, Any]] = None
@@ -0,0 +1,113 @@
1
+ import json
2
+ import logging
3
+ from functools import lru_cache
4
+
5
+ from pydantic import field_validator, model_validator
6
+ from pydantic_settings import BaseSettings
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class AuthSettings(BaseSettings):
12
+ """
13
+ Environment variables for the authentication module.
14
+
15
+ Required
16
+ --------
17
+ AUTH_ADMIN_API_KEY Admin api-key (super-key, always required).
18
+
19
+ Optional
20
+ --------
21
+ AUTH_API_KEYS JSON object mapping raw api-keys to their labels/roles.
22
+ Format: '{"key-abc": "reports", "key-xyz": "billing"}'
23
+ Labels are used with valid_token_types in AuthDependency.
24
+
25
+ AUTH_JWT_SECRET_KEY HMAC secret key for verifying user JWTs.
26
+ Required only when using "jwt" in valid_token_types.
27
+
28
+ AUTH_JWT_ALGORITHMS List of allowed HMAC algorithms (default: HS256/384/512).
29
+ """
30
+
31
+ # --- Admin api-key (required) ---
32
+ AUTH_ADMIN_API_KEY: str
33
+
34
+ # --- Additional api-keys: {"<raw-key>": "<label>", ...} ---
35
+ AUTH_API_KEYS: dict[str, str] = {}
36
+
37
+ # --- JWT ---
38
+ AUTH_JWT_SECRET_KEY: str = ""
39
+ AUTH_JWT_ALGORITHMS: list[str] = ["HS256", "HS384", "HS512"]
40
+
41
+ @field_validator("AUTH_API_KEYS", mode="before")
42
+ @classmethod
43
+ def _parse_api_keys(cls, v: object) -> dict[str, str]:
44
+ """Accepts the envvar as a JSON string or as a dict (useful for test injection)."""
45
+ if isinstance(v, str):
46
+ v = v.strip()
47
+ if not v:
48
+ return {}
49
+ try:
50
+ parsed = json.loads(v)
51
+ except json.JSONDecodeError as exc:
52
+ raise ValueError(
53
+ f"AUTH_API_KEYS must be a valid JSON string: {exc}. "
54
+ 'Example: \'{"key-abc": "reports", "key-xyz": "billing"}\''
55
+ ) from exc
56
+ if not isinstance(parsed, dict):
57
+ raise ValueError("AUTH_API_KEYS must be a JSON object (dict).")
58
+ return parsed
59
+ if isinstance(v, dict):
60
+ return v
61
+ raise ValueError(f"Unexpected type for AUTH_API_KEYS: {type(v)}")
62
+
63
+ @field_validator("AUTH_API_KEYS")
64
+ @classmethod
65
+ def _validate_api_keys_structure(cls, v: dict[str, str]) -> dict[str, str]:
66
+ """Ensures all keys and labels are non-empty strings."""
67
+ for key, label in v.items():
68
+ if not isinstance(key, str) or not key.strip():
69
+ raise ValueError(f"Api-key '{key}' cannot be an empty string.")
70
+ if not isinstance(label, str) or not label.strip():
71
+ raise ValueError(
72
+ f"Label for api-key '{key}' cannot be an empty string."
73
+ )
74
+ return v
75
+
76
+ @model_validator(mode="after")
77
+ def _warn_duplicate_keys(self) -> "AuthSettings":
78
+ """Warns if any additional key shares the same value as the ADMIN key."""
79
+ duplicates = [k for k in self.AUTH_API_KEYS if k == self.AUTH_ADMIN_API_KEY]
80
+ if duplicates:
81
+ logger.warning(
82
+ "AUTH: One of the AUTH_API_KEYS has the same value as AUTH_ADMIN_API_KEY (admin). "
83
+ "Consider using unique keys per role."
84
+ )
85
+ return self
86
+
87
+ # ------------------------------------------------------------------
88
+ # Query helpers (used by AuthDependency)
89
+ # ------------------------------------------------------------------
90
+
91
+ def label_for_key(self, api_key: str) -> str | None:
92
+ """
93
+ Returns the label/role associated with an api-key.
94
+ The ADMIN key always returns "admin".
95
+ """
96
+ if api_key == self.AUTH_ADMIN_API_KEY:
97
+ return "admin"
98
+ return self.AUTH_API_KEYS.get(api_key)
99
+
100
+ def keys_for_label(self, label: str) -> set[str]:
101
+ """Returns all api-keys that share a given label."""
102
+ return {k for k, lbl in self.AUTH_API_KEYS.items() if lbl == label}
103
+
104
+ def all_extra_labels(self) -> set[str]:
105
+ """Returns the set of unique labels defined in AUTH_API_KEYS."""
106
+ return set(self.AUTH_API_KEYS.values())
107
+
108
+ model_config = {"env_file": ".env", "extra": "ignore"}
109
+
110
+
111
+ @lru_cache
112
+ def get_auth_settings() -> AuthSettings:
113
+ return AuthSettings()
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-auth-manager-dep
3
+ Version: 0.1.0
4
+ Summary: Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support.
5
+ Author-email: Edmundo Andrade <edmon.af@gmail.com>
6
+ Project-URL: Homepage, https://github.com/edmon1024/fastapi-auth-manager-dep
7
+ Project-URL: Documentation, https://github.com/edmon1024/fastapi-auth-manager-dep
8
+ Project-URL: Repository, https://github.com/edmon1024/fastapi-auth-manager-dep
9
+ Project-URL: Issues, https://github.com/edmon1024/fastapi-auth-manager-dep/issues
10
+ Project-URL: Changelog, https://github.com/edmon1024/fastapi-auth-manager-dep/CHANGELOG.md
11
+ Keywords: fastapi,fastapi-auth-middleware,fastapi-jwt-auth,fastapi-jwt
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Internet
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: fastapi>=0.100.0
30
+ Requires-Dist: pydantic>=2.0.0
31
+ Requires-Dist: pydantic-settings>=2.0.0
32
+ Requires-Dist: pyjwt>=2.4.0
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest>=7.0.0; extra == "test"
35
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
36
+ Requires-Dist: httpx>=0.24.0; extra == "test"
37
+ Dynamic: license-file
38
+
39
+ # fastapi-auth-manager-dep
40
+
41
+ Reusable authentication dependency for FastAPI with **API Key** and **JWT Bearer** support.
42
+
43
+ - One mandatory ADMIN key via envvar (super-key, always valid)
44
+ - Additional api-keys with labels/roles via JSON envvar
45
+ - Fine-grained per-endpoint control: which roles each endpoint accepts
46
+ - HMAC JWT with configurable algorithms
47
+ - Public endpoints via explicit opt-out
48
+
49
+ ---
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install fastapi-auth-manager-dep
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Environment variables
60
+
61
+ | Variable | Required | Description |
62
+ |-------------------|----------|-------------------------------------------------------------------------------|
63
+ | `AUTH_ADMIN_API_KEY` | Yes | Administrator api-key. Super-key: valid on every endpoint. |
64
+ | `AUTH_API_KEYS` | No | JSON object mapping api-keys to their labels. See format below. |
65
+ | `AUTH_JWT_SECRET_KEY`| No* | HMAC secret key for verifying user JWTs. Required when using `"jwt"`. |
66
+ | `AUTH_JWT_ALGORITHMS` | No | List of allowed algorithms (default: `["HS256", "HS384", "HS512"]`). |
67
+
68
+ ### `AUTH_API_KEYS` format
69
+
70
+ A JSON object where each key is the raw api-key value and the value is its label/role:
71
+
72
+ ```bash
73
+ AUTH_API_KEYS='{"key-abc123": "reports", "key-xyz789": "billing", "key-qrs456": "billing"}'
74
+ ```
75
+
76
+ - Multiple keys can share the same label (same role).
77
+ - Empty string (`AUTH_API_KEYS=""`) is equivalent to having no additional keys.
78
+
79
+ ### `.env` example
80
+
81
+ ```dotenv
82
+ AUTH_ADMIN_API_KEY=super-secret-admin-key
83
+ AUTH_API_KEYS={"key-reports-1": "reports", "key-billing-1": "billing", "key-billing-2": "billing"}
84
+ AUTH_JWT_SECRET_KEY=my-jwt-secret-key
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Usage
90
+
91
+ ### 1. Global authentication (entire app)
92
+
93
+ All endpoints are protected with the ADMIN key by default:
94
+
95
+ ```python
96
+ from fastapi import Depends, FastAPI
97
+ from fastapi_auth import AuthDependency
98
+
99
+ app = FastAPI(dependencies=[Depends(AuthDependency())])
100
+ ```
101
+
102
+ ### 2. Restrict by api-key label
103
+
104
+ Only the ADMIN key and keys labelled `"reports"` can access:
105
+
106
+ ```python
107
+ from fastapi_auth import AuthDependency
108
+
109
+ @app.get(
110
+ "/reports",
111
+ dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))]
112
+ )
113
+ async def get_reports():
114
+ ...
115
+ ```
116
+
117
+ ### 3. Combine api-key and JWT on the same endpoint
118
+
119
+ ```python
120
+ from fastapi_auth import AuthDependency
121
+
122
+ @app.get(
123
+ "/billing",
124
+ dependencies=[Depends(AuthDependency(valid_token_types={"billing", "jwt"}))]
125
+ )
126
+ async def get_billing():
127
+ ...
128
+ ```
129
+
130
+ ### 4. Allow all additional api-keys
131
+
132
+ ```python
133
+ from fastapi_auth import AuthDependency
134
+
135
+ # Using the AuthDependency.ALL sentinel
136
+ @app.get(
137
+ "/any",
138
+ dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))]
139
+ )
140
+ async def any_key_endpoint():
141
+ ...
142
+
143
+ # Equivalent using a string
144
+ AuthDependency(valid_token_types="*")
145
+ ```
146
+
147
+ ### 5. Access the authenticated principal inside a handler
148
+
149
+ `AuthDependency` returns an `AuthPrincipal` with the method used, role, and JWT payload:
150
+
151
+ ```python
152
+ from fastapi_auth import AuthDependency, AuthPrincipal
153
+
154
+ @app.get("/me")
155
+ async def me(
156
+ principal: AuthPrincipal = Depends(
157
+ AuthDependency(valid_token_types={"jwt", "billing"})
158
+ )
159
+ ):
160
+ return {
161
+ "method": principal.method, # "jwt" | "api_key"
162
+ "sub": principal.sub, # user_id (JWT) or raw key value (api-key)
163
+ "role": principal.role, # "admin" | "reports" | "billing" | None (JWT)
164
+ "payload": principal.payload, # full JWT dict | None
165
+ }
166
+ ```
167
+
168
+ ### 6. Public endpoint (opt-out of global auth)
169
+
170
+ When auth is configured globally, use `PublicRoute` to exclude specific endpoints:
171
+
172
+ ```python
173
+ from fastapi_auth import PublicRoute
174
+
175
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
176
+ async def health():
177
+ return {"status": "ok"}
178
+ ```
179
+
180
+ ---
181
+
182
+ ## `valid_token_types` behaviour reference
183
+
184
+ | `valid_token_types` | ADMIN key | Additional keys | User JWT |
185
+ |------------------------------|-----------|-------------------------|----------|
186
+ | `None` (default) | Yes | No | No |
187
+ | `{"reports"}` | Yes | `reports` label only | No |
188
+ | `{"billing", "jwt"}` | Yes | `billing` label only | Yes |
189
+ | `AuthDependency.ALL` / `"*"` | Yes | All | No |
190
+ | `{"jwt"}` | Yes | No | Yes |
191
+
192
+ > The ADMIN key is always valid regardless of the endpoint configuration.
193
+
194
+ ---
195
+
196
+ ## `AuthPrincipal` — return object
197
+
198
+ ```python
199
+ class AuthPrincipal(BaseModel):
200
+ method: AuthMethod # AuthMethod.JWT | AuthMethod.AUTH_ADMIN_API_KEY
201
+ sub: str # user_id (JWT) or raw api-key value
202
+ role: str | None = None # key role/label; None for JWT
203
+ payload: dict | None = None # full JWT payload; None for api-key
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Error responses
209
+
210
+ All authentication errors return HTTP `401 Unauthorized` with a `detail` field:
211
+
212
+ | Situation | `detail` |
213
+ |--------------------------------------|-------------------------------------------|
214
+ | No credentials provided | `"Authentication credentials are required"` |
215
+ | Api-key not found or not allowed | `"Invalid authentication credentials"` |
216
+ | Expired JWT | `"JWT token has expired"` |
217
+ | Invalid JWT signature | `"Invalid JWT token: ..."` |
218
+ | Disallowed JWT algorithm | `"JWT algorithm not allowed: RS256"` |
219
+
220
+ ---
221
+
222
+ ## Tests
223
+
224
+ ```bash
225
+ pip install pytest pytest-asyncio
226
+ pytest tests/ -v
227
+ ```
228
+
229
+ Test coverage includes:
230
+
231
+ - ADMIN key as super-key across endpoints with different restrictions
232
+ - Label-based restriction: accepts the correct label, rejects others
233
+ - Multiple keys sharing the same label
234
+ - `ALL` sentinel and its `"*"` string equivalent
235
+ - Valid JWT, expired JWT, JWT ignored when `"jwt"` is not enabled
236
+ - No credentials
237
+ - `AUTH_API_KEYS` validation in settings (JSON string, dict, invalid JSON)
238
+
239
+ ---
240
+
241
+ ## Full example
242
+
243
+ ```python
244
+ from fastapi import Depends, FastAPI
245
+ from fastapi_auth import AuthDependency, AuthPrincipal, PublicRoute
246
+
247
+ app = FastAPI(dependencies=[Depends(AuthDependency())]) # global: ADMIN key only
248
+
249
+ @app.get("/health", dependencies=[Depends(PublicRoute())])
250
+ async def health():
251
+ return {"status": "ok"}
252
+
253
+ @app.get("/reports", dependencies=[Depends(AuthDependency(valid_token_types={"reports"}))])
254
+ async def reports():
255
+ return {"data": "..."}
256
+
257
+ @app.get("/me")
258
+ async def me(
259
+ principal: AuthPrincipal = Depends(AuthDependency(valid_token_types={"jwt"}))
260
+ ):
261
+ return {"sub": principal.sub, "payload": principal.payload}
262
+
263
+ @app.get("/internal", dependencies=[Depends(AuthDependency(valid_token_types=AuthDependency.ALL))])
264
+ async def internal():
265
+ return {"message": "any valid api-key accepted"}
266
+ ```
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ src/fastapi_auth/__init__.py
6
+ src/fastapi_auth/dependency.py
7
+ src/fastapi_auth/enums.py
8
+ src/fastapi_auth/public.py
9
+ src/fastapi_auth/schemas.py
10
+ src/fastapi_auth/settings.py
11
+ src/fastapi_auth_manager_dep.egg-info/PKG-INFO
12
+ src/fastapi_auth_manager_dep.egg-info/SOURCES.txt
13
+ src/fastapi_auth_manager_dep.egg-info/dependency_links.txt
14
+ src/fastapi_auth_manager_dep.egg-info/requires.txt
15
+ src/fastapi_auth_manager_dep.egg-info/top_level.txt
@@ -0,0 +1,9 @@
1
+ fastapi>=0.100.0
2
+ pydantic>=2.0.0
3
+ pydantic-settings>=2.0.0
4
+ pyjwt>=2.4.0
5
+
6
+ [test]
7
+ pytest>=7.0.0
8
+ pytest-asyncio>=0.21.0
9
+ httpx>=0.24.0