Zurück zur Startseite

JWT-Autorisierung FastAPI: Code und Schemas

Der Artikel beschreibt detailliert die Implementierung der JWT-Authentifizierung in FastAPI mit Access- und Refresh-Tokens. Vollständiger Code bereitgestellt: Konfiguration, Sicherheitsfunktionen, Pydantic-Schemas, Router und Abhängigkeiten. Projektarchitektur in Schichten für Skalierbarkeit unterteilt.

JWT in FastAPI: von Registrierung bis zu geschützten Endpunkten
Advertisement 728x90

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).

Google AdInline article slot
{
  "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:

Google AdInline article slot
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:

Google AdInline article slot
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

Advertisement 728x90

Weiterlesen