pypi-fastapi

Description
FastAPI framework, high performance, easy to learn, fast to code, ready for production
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/pypi-fastapi@0.116.0

security-authentication.md docs/

1
# Security & Authentication
2
3
FastAPI provides comprehensive security and authentication components that integrate seamlessly with dependency injection and automatic OpenAPI documentation generation. These components support various authentication schemes including API keys, HTTP authentication, and OAuth2.
4
5
## Capabilities
6
7
### Security Base Function
8
9
Core function for declaring security dependencies with optional scopes for fine-grained permission control.
10
11
```python { .api }
12
def Security(
13
dependency: Callable = None,
14
*,
15
scopes: List[str] = None,
16
use_cache: bool = True
17
) -> Any:
18
"""
19
Declare security dependencies with scopes.
20
21
Parameters:
22
- dependency: Security dependency callable
23
- scopes: List of required security scopes
24
- use_cache: Whether to cache dependency results
25
26
Returns:
27
Security dependency with scope validation
28
"""
29
```
30
31
### Security Scopes Class
32
33
Class for handling and validating security scopes in authentication systems.
34
35
```python { .api }
36
class SecurityScopes:
37
def __init__(self, scopes: List[str] = None) -> None:
38
"""
39
Security scopes container.
40
41
Parameters:
42
- scopes: List of security scopes
43
"""
44
self.scopes = scopes or []
45
self.scope_str = " ".join(self.scopes)
46
```
47
48
### API Key Authentication
49
50
Classes for implementing API key authentication via different transport mechanisms.
51
52
```python { .api }
53
class APIKeyQuery:
54
def __init__(
55
self,
56
*,
57
name: str,
58
scheme_name: str = None,
59
description: str = None,
60
auto_error: bool = True
61
) -> None:
62
"""
63
API key authentication via query parameters.
64
65
Parameters:
66
- name: Query parameter name for the API key
67
- scheme_name: Security scheme name for OpenAPI
68
- description: Security scheme description
69
- auto_error: Automatically raise HTTPException on authentication failure
70
"""
71
72
async def __call__(self, request: Request) -> str:
73
"""Extract and validate API key from query parameters."""
74
75
class APIKeyHeader:
76
def __init__(
77
self,
78
*,
79
name: str,
80
scheme_name: str = None,
81
description: str = None,
82
auto_error: bool = True
83
) -> None:
84
"""
85
API key authentication via headers.
86
87
Parameters:
88
- name: Header name for the API key
89
- scheme_name: Security scheme name for OpenAPI
90
- description: Security scheme description
91
- auto_error: Automatically raise HTTPException on authentication failure
92
"""
93
94
async def __call__(self, request: Request) -> str:
95
"""Extract and validate API key from headers."""
96
97
class APIKeyCookie:
98
def __init__(
99
self,
100
*,
101
name: str,
102
scheme_name: str = None,
103
description: str = None,
104
auto_error: bool = True
105
) -> None:
106
"""
107
API key authentication via cookies.
108
109
Parameters:
110
- name: Cookie name for the API key
111
- scheme_name: Security scheme name for OpenAPI
112
- description: Security scheme description
113
- auto_error: Automatically raise HTTPException on authentication failure
114
"""
115
116
async def __call__(self, request: Request) -> str:
117
"""Extract and validate API key from cookies."""
118
```
119
120
### HTTP Authentication
121
122
Classes for implementing standard HTTP authentication schemes.
123
124
```python { .api }
125
class HTTPBasic:
126
def __init__(
127
self,
128
*,
129
scheme_name: str = None,
130
realm: str = None,
131
description: str = None,
132
auto_error: bool = True
133
) -> None:
134
"""
135
HTTP Basic authentication.
136
137
Parameters:
138
- scheme_name: Security scheme name for OpenAPI
139
- realm: Authentication realm
140
- description: Security scheme description
141
- auto_error: Automatically raise HTTPException on authentication failure
142
"""
143
144
async def __call__(self, request: Request) -> HTTPBasicCredentials:
145
"""Extract and validate Basic authentication credentials."""
146
147
class HTTPBasicCredentials:
148
def __init__(self, username: str, password: str) -> None:
149
"""
150
HTTP Basic authentication credentials.
151
152
Parameters:
153
- username: Username from Basic auth
154
- password: Password from Basic auth
155
"""
156
self.username = username
157
self.password = password
158
159
class HTTPBearer:
160
def __init__(
161
self,
162
*,
163
bearerFormat: str = None,
164
scheme_name: str = None,
165
description: str = None,
166
auto_error: bool = True
167
) -> None:
168
"""
169
HTTP Bearer token authentication.
170
171
Parameters:
172
- bearerFormat: Bearer token format (e.g., "JWT")
173
- scheme_name: Security scheme name for OpenAPI
174
- description: Security scheme description
175
- auto_error: Automatically raise HTTPException on authentication failure
176
"""
177
178
async def __call__(self, request: Request) -> HTTPAuthorizationCredentials:
179
"""Extract and validate Bearer token credentials."""
180
181
class HTTPAuthorizationCredentials:
182
def __init__(self, scheme: str, credentials: str) -> None:
183
"""
184
HTTP authorization credentials.
185
186
Parameters:
187
- scheme: Authorization scheme (e.g., "Bearer")
188
- credentials: Authorization credentials (e.g., token)
189
"""
190
self.scheme = scheme
191
self.credentials = credentials
192
193
class HTTPDigest:
194
def __init__(
195
self,
196
*,
197
scheme_name: str = None,
198
realm: str = None,
199
description: str = None,
200
auto_error: bool = True
201
) -> None:
202
"""
203
HTTP Digest authentication.
204
205
Parameters:
206
- scheme_name: Security scheme name for OpenAPI
207
- realm: Authentication realm
208
- description: Security scheme description
209
- auto_error: Automatically raise HTTPException on authentication failure
210
"""
211
212
async def __call__(self, request: Request) -> HTTPAuthorizationCredentials:
213
"""Extract and validate Digest authentication credentials."""
214
```
215
216
### OAuth2 Authentication
217
218
Classes for implementing OAuth2 authentication flows.
219
220
```python { .api }
221
class OAuth2:
222
def __init__(
223
self,
224
*,
225
flows: Dict[str, Dict[str, Any]] = None,
226
scheme_name: str = None,
227
description: str = None,
228
auto_error: bool = True
229
) -> None:
230
"""
231
OAuth2 authentication base class.
232
233
Parameters:
234
- flows: OAuth2 flows configuration
235
- scheme_name: Security scheme name for OpenAPI
236
- description: Security scheme description
237
- auto_error: Automatically raise HTTPException on authentication failure
238
"""
239
240
class OAuth2PasswordBearer:
241
def __init__(
242
self,
243
tokenUrl: str,
244
*,
245
scheme_name: str = None,
246
scopes: Dict[str, str] = None,
247
description: str = None,
248
auto_error: bool = True
249
) -> None:
250
"""
251
OAuth2 password bearer authentication.
252
253
Parameters:
254
- tokenUrl: URL for token endpoint
255
- scheme_name: Security scheme name for OpenAPI
256
- scopes: Available OAuth2 scopes
257
- description: Security scheme description
258
- auto_error: Automatically raise HTTPException on authentication failure
259
"""
260
261
async def __call__(self, request: Request) -> str:
262
"""Extract and validate OAuth2 bearer token."""
263
264
class OAuth2AuthorizationCodeBearer:
265
def __init__(
266
self,
267
authorizationUrl: str,
268
tokenUrl: str,
269
*,
270
refreshUrl: str = None,
271
scheme_name: str = None,
272
scopes: Dict[str, str] = None,
273
description: str = None,
274
auto_error: bool = True
275
) -> None:
276
"""
277
OAuth2 authorization code bearer authentication.
278
279
Parameters:
280
- authorizationUrl: URL for authorization endpoint
281
- tokenUrl: URL for token endpoint
282
- refreshUrl: URL for token refresh endpoint
283
- scheme_name: Security scheme name for OpenAPI
284
- scopes: Available OAuth2 scopes
285
- description: Security scheme description
286
- auto_error: Automatically raise HTTPException on authentication failure
287
"""
288
289
async def __call__(self, request: Request) -> str:
290
"""Extract and validate OAuth2 authorization code bearer token."""
291
292
class OAuth2PasswordRequestForm:
293
def __init__(
294
self,
295
*,
296
grant_type: str = Form(regex="password"),
297
username: str = Form(),
298
password: str = Form(),
299
scope: str = Form(""),
300
client_id: str = Form(None),
301
client_secret: str = Form(None)
302
) -> None:
303
"""
304
OAuth2 password request form.
305
306
Parameters:
307
- grant_type: OAuth2 grant type (must be "password")
308
- username: User username
309
- password: User password
310
- scope: Requested scopes
311
- client_id: OAuth2 client ID
312
- client_secret: OAuth2 client secret
313
"""
314
315
class OAuth2PasswordRequestFormStrict:
316
def __init__(
317
self,
318
*,
319
grant_type: str = Form(regex="password"),
320
username: str = Form(),
321
password: str = Form(),
322
scope: str = Form(""),
323
client_id: str = Form(),
324
client_secret: str = Form()
325
) -> None:
326
"""
327
Strict OAuth2 password request form with required client credentials.
328
329
Parameters:
330
- grant_type: OAuth2 grant type (must be "password")
331
- username: User username
332
- password: User password
333
- scope: Requested scopes
334
- client_id: OAuth2 client ID (required)
335
- client_secret: OAuth2 client secret (required)
336
"""
337
```
338
339
### OpenID Connect Authentication
340
341
Class for implementing OpenID Connect authentication.
342
343
```python { .api }
344
class OpenIdConnect:
345
def __init__(
346
self,
347
*,
348
openIdConnectUrl: str,
349
scheme_name: str = None,
350
description: str = None,
351
auto_error: bool = True
352
) -> None:
353
"""
354
OpenID Connect authentication.
355
356
Parameters:
357
- openIdConnectUrl: OpenID Connect discovery URL
358
- scheme_name: Security scheme name for OpenAPI
359
- description: Security scheme description
360
- auto_error: Automatically raise HTTPException on authentication failure
361
"""
362
363
async def __call__(self, request: Request) -> str:
364
"""Extract and validate OpenID Connect token."""
365
```
366
367
## Usage Examples
368
369
### API Key Authentication
370
371
```python
372
from fastapi import FastAPI, Depends, HTTPException, status
373
from fastapi.security import APIKeyHeader
374
375
app = FastAPI()
376
377
API_KEY = "your-secret-api-key"
378
api_key_header = APIKeyHeader(name="X-API-Key")
379
380
def verify_api_key(api_key: str = Depends(api_key_header)):
381
if api_key != API_KEY:
382
raise HTTPException(
383
status_code=status.HTTP_401_UNAUTHORIZED,
384
detail="Invalid API Key"
385
)
386
return api_key
387
388
@app.get("/protected")
389
def protected_route(api_key: str = Depends(verify_api_key)):
390
return {"message": "This is a protected route", "api_key": api_key}
391
```
392
393
### HTTP Basic Authentication
394
395
```python
396
import secrets
397
from fastapi import FastAPI, Depends, HTTPException, status
398
from fastapi.security import HTTPBasic, HTTPBasicCredentials
399
400
app = FastAPI()
401
402
security = HTTPBasic()
403
404
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
405
current_username_bytes = credentials.username.encode("utf8")
406
correct_username_bytes = b"testuser"
407
is_correct_username = secrets.compare_digest(
408
current_username_bytes, correct_username_bytes
409
)
410
current_password_bytes = credentials.password.encode("utf8")
411
correct_password_bytes = b"testpass"
412
is_correct_password = secrets.compare_digest(
413
current_password_bytes, correct_password_bytes
414
)
415
if not (is_correct_username and is_correct_password):
416
raise HTTPException(
417
status_code=status.HTTP_401_UNAUTHORIZED,
418
detail="Incorrect username or password",
419
headers={"WWW-Authenticate": "Basic"},
420
)
421
return credentials.username
422
423
@app.get("/users/me")
424
def read_current_user(username: str = Depends(get_current_username)):
425
return {"username": username}
426
```
427
428
### HTTP Bearer Token Authentication
429
430
```python
431
from fastapi import FastAPI, Depends, HTTPException, status
432
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
433
434
app = FastAPI()
435
436
security = HTTPBearer()
437
438
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
439
token = credentials.credentials
440
if token != "valid-bearer-token":
441
raise HTTPException(
442
status_code=status.HTTP_401_UNAUTHORIZED,
443
detail="Invalid authentication token"
444
)
445
return token
446
447
@app.get("/protected")
448
def protected_route(token: str = Depends(verify_token)):
449
return {"message": "Access granted", "token": token}
450
```
451
452
### OAuth2 Password Bearer Authentication
453
454
```python
455
from datetime import datetime, timedelta
456
from jose import JWTError, jwt
457
from passlib.context import CryptContext
458
from fastapi import FastAPI, Depends, HTTPException, status
459
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
460
461
app = FastAPI()
462
463
SECRET_KEY = "your-secret-key"
464
ALGORITHM = "HS256"
465
ACCESS_TOKEN_EXPIRE_MINUTES = 30
466
467
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
468
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
469
470
fake_users_db = {
471
"testuser": {
472
"username": "testuser",
473
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
474
"email": "test@example.com",
475
}
476
}
477
478
def verify_password(plain_password, hashed_password):
479
return pwd_context.verify(plain_password, hashed_password)
480
481
def get_password_hash(password):
482
return pwd_context.hash(password)
483
484
def get_user(db, username: str):
485
if username in db:
486
user_dict = db[username]
487
return user_dict
488
489
def authenticate_user(fake_db, username: str, password: str):
490
user = get_user(fake_db, username)
491
if not user:
492
return False
493
if not verify_password(password, user["hashed_password"]):
494
return False
495
return user
496
497
def create_access_token(data: dict, expires_delta: timedelta = None):
498
to_encode = data.copy()
499
if expires_delta:
500
expire = datetime.utcnow() + expires_delta
501
else:
502
expire = datetime.utcnow() + timedelta(minutes=15)
503
to_encode.update({"exp": expire})
504
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
505
return encoded_jwt
506
507
async def get_current_user(token: str = Depends(oauth2_scheme)):
508
credentials_exception = HTTPException(
509
status_code=status.HTTP_401_UNAUTHORIZED,
510
detail="Could not validate credentials",
511
headers={"WWW-Authenticate": "Bearer"},
512
)
513
try:
514
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
515
username: str = payload.get("sub")
516
if username is None:
517
raise credentials_exception
518
except JWTError:
519
raise credentials_exception
520
user = get_user(fake_users_db, username=username)
521
if user is None:
522
raise credentials_exception
523
return user
524
525
@app.post("/token")
526
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
527
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
528
if not user:
529
raise HTTPException(
530
status_code=status.HTTP_401_UNAUTHORIZED,
531
detail="Incorrect username or password",
532
headers={"WWW-Authenticate": "Bearer"},
533
)
534
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
535
access_token = create_access_token(
536
data={"sub": user["username"]}, expires_delta=access_token_expires
537
)
538
return {"access_token": access_token, "token_type": "bearer"}
539
540
@app.get("/users/me")
541
async def read_users_me(current_user: dict = Depends(get_current_user)):
542
return current_user
543
```
544
545
### Security with Scopes
546
547
```python
548
from fastapi import FastAPI, Depends, HTTPException, status
549
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm, SecurityScopes
550
551
app = FastAPI()
552
553
oauth2_scheme = OAuth2PasswordBearer(
554
tokenUrl="token",
555
scopes={
556
"read": "Read access",
557
"write": "Write access",
558
"admin": "Admin access"
559
}
560
)
561
562
def get_current_user(
563
security_scopes: SecurityScopes,
564
token: str = Depends(oauth2_scheme)
565
):
566
if security_scopes.scopes:
567
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
568
else:
569
authenticate_value = "Bearer"
570
571
credentials_exception = HTTPException(
572
status_code=status.HTTP_401_UNAUTHORIZED,
573
detail="Could not validate credentials",
574
headers={"WWW-Authenticate": authenticate_value},
575
)
576
577
# Token validation logic here
578
# For demo purposes, assume token is valid and contains scopes
579
token_scopes = ["read", "write"] # Scopes from decoded token
580
581
for scope in security_scopes.scopes:
582
if scope not in token_scopes:
583
raise HTTPException(
584
status_code=status.HTTP_401_UNAUTHORIZED,
585
detail="Not enough permissions",
586
headers={"WWW-Authenticate": authenticate_value},
587
)
588
589
return {"username": "testuser", "scopes": token_scopes}
590
591
@app.get("/read-data")
592
async def read_data(
593
current_user: dict = Security(get_current_user, scopes=["read"])
594
):
595
return {"data": "This requires read access"}
596
597
@app.post("/write-data")
598
async def write_data(
599
current_user: dict = Security(get_current_user, scopes=["write"])
600
):
601
return {"message": "Data written successfully"}
602
603
@app.delete("/admin-action")
604
async def admin_action(
605
current_user: dict = Security(get_current_user, scopes=["admin"])
606
):
607
return {"message": "Admin action performed"}
608
```
609
610
### Multiple Authentication Methods
611
612
```python
613
from fastapi import FastAPI, Depends, HTTPException, status
614
from fastapi.security import HTTPBearer, APIKeyHeader
615
from typing import Union
616
617
app = FastAPI()
618
619
bearer_scheme = HTTPBearer(auto_error=False)
620
api_key_scheme = APIKeyHeader(name="X-API-Key", auto_error=False)
621
622
async def get_current_user(
623
bearer_token: str = Depends(bearer_scheme),
624
api_key: str = Depends(api_key_scheme)
625
) -> dict:
626
# Try bearer token first
627
if bearer_token:
628
if bearer_token.credentials == "valid-bearer-token":
629
return {"username": "bearer_user", "auth_method": "bearer"}
630
631
# Try API key second
632
if api_key:
633
if api_key == "valid-api-key":
634
return {"username": "api_user", "auth_method": "api_key"}
635
636
# Neither authentication method worked
637
raise HTTPException(
638
status_code=status.HTTP_401_UNAUTHORIZED,
639
detail="Invalid authentication credentials"
640
)
641
642
@app.get("/protected")
643
async def protected_route(current_user: dict = Depends(get_current_user)):
644
return {
645
"message": f"Hello {current_user['username']}",
646
"auth_method": current_user["auth_method"]
647
}
648
```
649
650
### Custom Security Dependency
651
652
```python
653
from fastapi import FastAPI, Request, HTTPException, Depends, status
654
655
app = FastAPI()
656
657
class CustomAuth:
658
def __init__(self, required_role: str = None):
659
self.required_role = required_role
660
661
async def __call__(self, request: Request):
662
# Custom authentication logic
663
auth_header = request.headers.get("Authorization")
664
if not auth_header:
665
raise HTTPException(
666
status_code=status.HTTP_401_UNAUTHORIZED,
667
detail="Authorization header required"
668
)
669
670
# Validate custom token format
671
if not auth_header.startswith("Custom "):
672
raise HTTPException(
673
status_code=status.HTTP_401_UNAUTHORIZED,
674
detail="Invalid token format"
675
)
676
677
token = auth_header.replace("Custom ", "")
678
679
# Mock user validation
680
if token == "valid-custom-token":
681
user = {"username": "custom_user", "role": "admin"}
682
else:
683
raise HTTPException(
684
status_code=status.HTTP_401_UNAUTHORIZED,
685
detail="Invalid token"
686
)
687
688
# Check role if required
689
if self.required_role and user.get("role") != self.required_role:
690
raise HTTPException(
691
status_code=status.HTTP_403_FORBIDDEN,
692
detail="Insufficient permissions"
693
)
694
695
return user
696
697
# Use custom security
698
auth = CustomAuth()
699
admin_auth = CustomAuth(required_role="admin")
700
701
@app.get("/user-info")
702
async def get_user_info(user: dict = Depends(auth)):
703
return user
704
705
@app.get("/admin-only")
706
async def admin_only(user: dict = Depends(admin_auth)):
707
return {"message": "Admin access granted", "user": user}
708
```