Files
CleanArchitecture-template/.brain/.agent/skills/python-backend/references/security_patterns.md
2026-03-12 15:17:52 +07:00

131 lines
2.9 KiB
Markdown

# Security Patterns
Authentication and security patterns for Python/FastAPI backends.
## Password Hashing (passlib + bcrypt)
Never store plaintext passwords. Hash with bcrypt:
```python
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(password: str, password_hash: str) -> bool:
return pwd_context.verify(password, password_hash)
```
---
## JWT Create/Verify (python-jose)
Issue short-lived access tokens and validate them on each request:
```python
from datetime import datetime, timedelta, timezone
from jose import JWTError, jwt
JWT_ALG = "HS256"
JWT_SECRET = "change-me"
def create_access_token(*, subject: str, expires_minutes: int = 15) -> str:
now = datetime.now(timezone.utc)
payload = {
"sub": subject,
"iat": int(now.timestamp()),
"exp": int((now + timedelta(minutes=expires_minutes)).timestamp()),
}
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)
def decode_access_token(token: str) -> dict:
try:
return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
except JWTError as e:
raise ValueError("Invalid token") from e
```
---
## FastAPI OAuth2 Bearer Dependency
Use OAuth2PasswordBearer to parse `Authorization: Bearer <token>`:
```python
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
try:
payload = decode_access_token(token)
except ValueError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
return {"user_id": payload["sub"]}
```
---
## API Key Auth via Header
Protect internal endpoints with an API key header:
```python
from fastapi import Depends, Header, HTTPException, status
API_KEY = "change-me"
def require_api_key(
x_api_key: str | None = Header(default=None, alias="X-API-Key")
) -> None:
if not x_api_key or x_api_key != API_KEY:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid API key"
)
```
---
## CORS Configuration
Lock CORS down to known origins:
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
)
```
---
## Hide OpenAPI Docs by Default
Disable docs endpoints in production:
```python
from fastapi import FastAPI
ENV = "production" # e.g., from env vars
app = FastAPI(
title="My API",
openapi_url=None if ENV == "production" else "/openapi.json",
)
```