Salta al contenuto principale

Autenticazione con Token JWT in FastAPI: Un Approccio Completo

Profile picture for user luca77king

FastAPI è un framework moderno e potente per la creazione di API web, progettato per offrire alte prestazioni e una grande facilità d'uso. È costruito su Python 3.6+ e sfrutta le caratteristiche avanzate del linguaggio, come le annotazioni di tipo, per fornire un'esperienza di sviluppo molto snella e intuitiva. Una delle sue caratteristiche principali è la capacità di generare API veloci e sicure, riducendo al minimo la quantità di codice necessaria. Questo lo rende una scelta ideale per costruire applicazioni web, specialmente quando si tratta di implementare funzionalità avanzate come l'autenticazione degli utenti.

L'autenticazione è un aspetto fondamentale nella progettazione di qualsiasi sistema che gestisce dati sensibili. Senza un sistema di autenticazione adeguato, le API possono essere vulnerabili ad accessi non autorizzati, mettendo a rischio le informazioni degli utenti. Una delle tecniche più utilizzate per proteggere le risorse di un'API è l'autenticazione basata su token JWT (JSON Web Token). Questo metodo consente di autenticare gli utenti in modo sicuro e scalabile, senza la necessità di mantenere sessioni persistenti sul server. In questo articolo, esploreremo passo dopo passo come implementare l'autenticazione con token JWT in un'applicazione FastAPI, dal processo di generazione del token fino alla creazione delle rotte protette.

Inizieremo con una panoramica sul funzionamento di JWT e su come si integra con un'applicazione FastAPI. Poi, vedremo come gestire le credenziali degli utenti tramite variabili d'ambiente per mantenerle sicure e come creare le funzioni necessarie per la gestione dei token. Infine, implementeremo il routing per le rotte di login e per l'accesso alle risorse protette, assicurandoci che solo gli utenti autenticati possano interagire con queste risorse.

Come Funziona l'Autenticazione con Token JWT

Il flusso di autenticazione con JWT inizia con l'utente che invia le proprie credenziali (nome utente e password) al server. Se le credenziali sono corrette, il server crea un token JWT che contiene informazioni sull'utente (ad esempio, il nome utente) e lo restituisce come risposta. L'utente deve quindi includere questo token in ogni richiesta futura per accedere alle risorse protette.

Un JWT è composto da tre parti principali:

  1. Header: contiene il tipo di token (JWT) e l'algoritmo di firma.
  2. Payload: contiene le informazioni (dati) che vogliamo includere nel token, come il nome utente o la scadenza.
  3. Signature: è una firma crittografata che garantisce l'integrità del token.

Recupero delle Variabili d'Ambiente e Creazione delle Funzioni

In un'applicazione reale, non vogliamo esporre informazioni sensibili come il nome utente, la password hashata, e la chiave segreta direttamente nel codice. Per farlo, utilizziamo le variabili d'ambiente, che vengono caricate tramite un file .env. FastAPI può interagire facilmente con variabili d'ambiente grazie alla libreria python-dotenv, che ci permette di caricare le variabili direttamente nel nostro script.

Installazione librerie necessarie:

pip install fastapi
pip install pydantic
pip install python-dotenv
pip install python-jose
pip install passlib

Setup iniziale:

import os
import dotenv
from datetime import datetime, timedelta
from passlib.context import CryptContext
from jose import JWTError, jwt
from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from fastapi.security import OAuth2PasswordBearer

dotenv.load_dotenv()

try:
    USERNAME = os.getenv("USERNAME")
    PASSWORD_HASH = os.getenv("PASSWORD_HASH")
except KeyError as e:
    print(f"Missing {e} in environment variables")

SECRET_KEY = os.getenv("SECRET_KEY")
ALGORITHM = os.getenv("ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 30))

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

In questo blocco di codice, carichiamo le variabili d'ambiente dal file .env utilizzando la funzione dotenv.load_dotenv(). Se le variabili come USERNAME, PASSWORD_HASH, e SECRET_KEY non sono definite, viene sollevata un'eccezione che indica un problema nella configurazione. Le variabili d'ambiente sono poi utilizzate per configurare il sistema di autenticazione.

  • USERNAME e PASSWORD_HASH sono necessari per validare l'utente.
  • SECRET_KEY viene utilizzata per firmare il nostro token JWT.
  • ALGORITHM specifica l'algoritmo di firma, che di solito è HS256.
  • ACCESS_TOKEN_EXPIRE_MINUTES imposta il tempo di scadenza del token (di default è 30 minuti).

Creazione delle Funzioni di Autenticazione

Successivamente, dobbiamo definire alcune funzioni per gestire il flusso di autenticazione, come la verifica della password e la generazione del token.

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or
timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

        if "exp" in payload and datetime.utcfromtimestamp(payload["exp"]) < datetime.utcnow():
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token has expired",
            )

        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
        )
  • verify_password: verifica che la password in chiaro corrisponda alla password hashata.
  • create_access_token: crea un token JWT a partire dai dati dell'utente, includendo la scadenza.

Routing in FastAPI: Creazione delle Rotte per il Login e le Risorse Protette

In FastAPI, possiamo definire facilmente le rotte per gestire il flusso di autenticazione e le risorse protette. Dobbiamo creare una rotta che consenta all'utente di accedere tramite una richiesta POST e ricevere un token, e una rotta protetta che accetta solo richieste valide con token.

Rotta di Login (POST /token)

La rotta di login è quella che permette all'utente di ottenere il token JWT. L'utente invia le proprie credenziali (nome utente e password) tramite una richiesta POST, e se le credenziali sono valide, il server genera un token JWT e lo restituisce.

from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel

app = FastAPI()

class Token(BaseModel):
    access_token: str
    token_type: str

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    if form_data.username != USER_USERNAME or not verify_password(form_data.password, USER_PASSWORD_HASH):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials",
        )
    
    access_token = create_access_token(data={"sub": form_data.username})
    return {"access_token": access_token, "token_type": "bearer"}

La funzione login_for_access_token si occupa di verificare le credenziali dell'utente. Se sono corrette, genera un token che viene restituito come risposta.

Rotta Protetta (GET /protected)

Una volta che l'utente ha ottenuto il token, deve utilizzarlo per accedere alle risorse protette. La rotta /protected è una rotta che richiede un token valido per consentire l'accesso.

@app.get("/protected")
async def read_protected_data(token: str = Depends(oauth2_scheme)):
    payload = verify_token(token)
    if not payload:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
        )
    return {"message": "Access granted to protected data", "user":
payload["sub"]}

In questo caso, la funzione read_protected_data dipende dal token, che viene passato tramite l'header di autorizzazione. Il token viene verificato tramite la funzione verify_token. Se il token è valido, l'accesso alla risorsa viene concesso.

Conclusioni

Abbiamo visto come implementare un sistema di autenticazione sicuro in FastAPI utilizzando token JWT. La configurazione delle variabili d'ambiente e la gestione delle credenziali sono aspetti cruciali per mantenere la sicurezza del sistema. Inoltre, la gestione delle rotte per il login e le risorse protette ci consente di creare un flusso completo di autenticazione. Con questo sistema, possiamo facilmente proteggere le API e garantire che solo gli utenti autorizzati possano accedere ai dati sensibili.