JWT-Authentifizierung in FastAPI implementieren: Kompletter Leitfaden mit Code
JWT (JSON Web Token) ermöglicht eine stateless Authentifizierung in APIs. Ein Token besteht aus Header, Payload und Signatur – base64-codiert und durch Punkte getrennt.
Header legt den Signaturalgorithmus fest:
{
"alg": "HS256",
"typ": "JWT"
}
Payload enthält Claims wie user_id (sub), Ausstellungszeit (iat) und Ablaufzeit (exp).
{
"sub": "user_id_123",
"exp": 1735689600,
"iat": 1735603200
}
Signatur gewährleistet Integrität mittels HMAC-SHA256 mit einem geheimen Schlüssel.
Für Produktionsumgebungen empfiehlt sich ein Dual-Token-System:
- Zugriffstoken (15–30 Minuten) – für API-Anfragen
- Aktualisierungstoken (7–30 Tage) – zum Erneuern von Zugriffstoken
Projektarchitektur
Geschichtete Struktur vereinfacht Testen und Skalierung:
project/
├── app/
│ ├── core/ # config.py, security.py, dependencies.py
│ ├── models/ # user.py (SQLAlchemy)
│ ├── schemas/ # user.py, token.py (Pydantic)
│ ├── routers/ # auth.py, users.py
│ ├── services/ # user.py
│ └── main.py
├── requirements.txt
└── .env
Routers verarbeiten HTTP-Logik, Schemas validieren Eingaben, Modelle definieren die Datenbankstruktur und Services enthalten Geschäftsregeln.
Konfiguration und Sicherheits-Utilities
In core/config.py:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
SECRET_KEY: str = "your-secret-key-change-in-production"
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
class Config:
env_file = ".env"
settings = Settings()
Wichtige Funktionen in core/security.py:
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from .config import settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt
def create_refresh_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt
def decode_token(token: str) -> dict:
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
return payload
except JWTError:
return None
Pydantic-Schemas zur Validierung
schemas/user.py:
from pydantic import BaseModel, EmailStr
class UserCreate(BaseModel):
email: EmailStr
password: str
full_name: str
class UserResponse(BaseModel):
id: int
email: EmailStr
full_name: str
class Config:
from_attributes = True
schemas/token.py:
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
class TokenData(BaseModel):
user_id: int
Abhängigkeit für aktuellen Benutzer
core/dependencies.py implementiert OAuth2PasswordBearer:
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
payload = decode_token(token)
if payload is None:
raise credentials_exception
user_id = payload.get("sub")
if user_id is None:
raise credentials_exception
token_data = TokenData(user_id=int(user_id))
user = db.query(User).filter(User.id == token_data.user_id).first()
if user is None:
raise credentials_exception
return user
Authentifizierungs-Endpunkte
In routers/auth.py:
- POST /api/auth/register – erstellt einen Benutzer mit bcrypt-Hashing
- POST /api/auth/login – gibt Zugriffs- und Aktualisierungstoken zurück
- POST /api/auth/refresh – erneuert das Zugriffstoken
Vollständige Login-Implementierung:
@router.post("/login", response_model=Token)
def login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
user = db.query(User).filter(User.email == form_data.username).first()
if not user or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = create_access_token(data={"sub": str(user.id)})
refresh_token = create_refresh_token(data={"sub": str(user.id)})
return {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}
Geschützte Ressourcen
routers/users.py:
@router.get("/me", response_model=UserResponse)
def get_current_user_info(current_user: User = Depends(get_current_user)):
return current_user
Dieser Endpunkt erfordert ein Bearer-Token im Authorization-Header.
Hauptanwendung
main.py:
from fastapi import FastAPI
from .routers import auth, users
app = FastAPI(title="FastAPI JWT Auth", version="1.0.0")
app.include_router(auth.router)
app.include_router(users.router)
@app.get("/")
def root():
return {"message": "Welcome to FastAPI JWT Auth API"}
Wichtige Erkenntnisse
- Verwende HS256 mit einem starken SECRET_KEY (mindestens 32 Zeichen)
- Speichere Aktualisierungstokens in HttpOnly-Cookies, um XSS-Angriffe zu verhindern
- Implementiere Token-Rotation bei jeder Aktualisierung
- Protokolliere fehlgeschlagene Anmeldeversuche
- Teste abgelaufene Tokens (prüfe
exp-Claim)
— Editorial Team
Noch keine Kommentare.