In questa guida, esploreremo come utilizzare i JWT (JSON Web Token) in Python per migliorare l’autenticazione e la sicurezza, dalla generazione alla verifica. Il nostro obiettivo è quello di fornirti una comprensione completa del processo di autenticazione basato su JWT utilizzando le librerie Python, dalle basi fino agli esempi di implementazione e alle misure di sicurezza. Approfondiamo le conoscenze necessarie per costruire una funzionalità di autenticazione affidabile per le applicazioni web e lo sviluppo delle API.
Concetti di base e funzionamento di JWT
JWT (JSON Web Token) è uno dei formati di token più utilizzati per l’autenticazione web. JWT si basa su uno standard di trasmissione sicura delle informazioni tra client e server (RFC 7519) ed è dotato di una funzionalità di protezione contro le manomissioni tramite firma.
Struttura di JWT
JWT è composto da tre parti separate da un punto (.
):
- Header
- Specifica il tipo di token (ad esempio: JWT) e l’algoritmo di firma (ad esempio: HS256).
{
"alg": "HS256",
"typ": "JWT"
}
- Payload
- Contiene i dati (claims) che si desiderano includere nel token in formato JSON.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
- Signature
- Firma generata utilizzando la chiave segreta o pubblica, combinando l’header e il payload. Ha lo scopo di prevenire manomissioni.
Principio di funzionamento di JWT
- Quando un client invia le credenziali di autenticazione al momento del login, il server genera un JWT basato su queste informazioni.
- Il JWT generato viene inviato al client e di solito viene memorizzato in un cookie o nell’intestazione HTTP.
- Il client invia il JWT al server con ogni richiesta, e il server verifica il token per autorizzare la richiesta.
Questo processo consente l’autenticazione senza stato, migliorando la scalabilità e la sicurezza.
Scelta della libreria JWT in Python
Esistono diverse librerie in Python per lavorare con i JWT. Ognuna di esse ha delle caratteristiche uniche, e scegliere quella giusta dipende dai requisiti del progetto.
Librerie principali
- PyJWT
- La libreria più comune e leggera. Consente di generare e verificare facilmente i JWT.
- Sito ufficiale: PyJWT GitHub
- Caratteristiche:
- API semplice e facile da usare
- Sostiene vari algoritmi di firma, tra cui HMAC (HS256, HS512) e RSA (RS256, RS512)
- Installazione:
bash pip install pyjwt
- Authlib
- Supporta framework di autenticazione avanzati come OAuth2 e OpenID Connect, e supporta anche i JWT.
- Sito ufficiale: Authlib
- Caratteristiche:
- Funzionalità di sicurezza avanzate
- Specializzata nella gestione dei token
- Installazione:
bash pip install authlib
- python-jose
- Supporta JSON Web Token, JSON Web Signature (JWS), e JSON Web Encryption (JWE).
- Caratteristiche:
- Supporto completo per la crittografia (JWE)
- Personalizzazione avanzata
- Installazione:
bash pip install python-jose
Come scegliere la libreria giusta
- Uso semplice per l’autenticazione: PyJWT è l’ideale. È leggero e intuitivo.
- Flussi di autenticazione avanzati: Authlib è consigliato se si utilizza OAuth o OpenID Connect.
- Necessità di crittografia: Se è necessario crittografare i token, python-jose è la scelta migliore.
In base alle dimensioni del progetto e ai requisiti di sicurezza, è possibile utilizzare queste librerie per sfruttare al meglio i JWT.
Implementazione della generazione di JWT in Python
Generare un JWT è semplice ed efficiente con le giuste librerie Python. In questa sezione, ti guideremo attraverso i passaggi per generare un JWT utilizzando la libreria PyJWT.
Generazione di un JWT di base con PyJWT
Il seguente codice mostra come generare un JWT utilizzando PyJWT:
import jwt
import datetime
# Impostazione della chiave segreta
SECRET_KEY = "your-secret-key"
# Creazione del payload
payload = {
"sub": "1234567890", # Identificativo utente
"name": "John Doe", # Informazioni utente
"iat": datetime.datetime.utcnow(), # Ora di emissione del token
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1) # Scadenza (un'ora)
}
# Generazione del token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("JWT generato:", token)
Spiegazione del codice
- Impostazione della chiave segreta
- Imposta la chiave segreta da usare per firmare il token. Deve essere gestita in modo sicuro all’interno del server.
- Creazione del payload
- Include i claims come
sub
ename
. - Può includere l’ora di emissione (
iat
) e la data di scadenza (exp
) se necessario.
- Generazione del token
- Il token viene generato usando
jwt.encode
, che codifica il payload e genera il token firmato. - Gli algoritmi di firma comuni includono
HS256
eRS256
.
Generazione di un JWT con firma RSA
Di seguito, un esempio di generazione di un JWT utilizzando una firma RSA con una chiave privata e pubblica.
import jwt
# Chiave privata e pubblica RSA
private_key = """-----BEGIN RSA PRIVATE KEY-----
...(contenuto della chiave privata)...
-----END RSA PRIVATE KEY-----"""
public_key = """-----BEGIN PUBLIC KEY-----
...(contenuto della chiave pubblica)...
-----END PUBLIC KEY-----"""
# Creazione del payload
payload = {
"sub": "1234567890",
"name": "John Doe",
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# Generazione del token con firma RSA
token = jwt.encode(payload, private_key, algorithm="RS256")
print("JWT generato con RSA:", token)
Considerazioni importanti
- Gestione della chiave segreta
- La chiave segreta deve essere gestita in modo sicuro e non deve essere mai esposta a terzi.
- Impostazione della scadenza
- Impostare sempre il campo
exp
per limitare la durata del token è fondamentale per la sicurezza.
- Dimensione del token
- Includere troppi dati nel payload può aumentare la dimensione del token, riducendo l’efficienza della comunicazione.
Usando questi esempi di codice, è possibile generare JWT adatti alle proprie necessità e costruire un sistema di autenticazione sicuro.
Progettazione dei dati nel payload di JWT
Il payload di JWT contiene informazioni utente o attributi del token. La progettazione del payload è cruciale per l’efficienza e la sicurezza del token.
Composizione di base del payload
Il payload di JWT può contenere tre tipi di “claims” (dichiarazioni):
- Claims registrati
- Claims standardizzati secondo le specifiche. Devono essere utilizzati correttamente.
- Esempi principali:
iss
(emittente): chi ha emesso il token.sub
(soggetto): il destinatario del token (es. ID utente).aud
(destinatario): il destinatario previsto del token (es. nome del servizio).exp
(scadenza): scadenza del token in formato timestamp UNIX.iat
(emissione): orario di emissione del token in formato timestamp UNIX.
- Claims pubblici
- Può contenere dati personalizzati per l’applicazione.
- Per evitare conflitti con altri sistemi, utilizzare uno spazio dei nomi (es.
namespace/attribute
).
- Claims privati
- Contiene dati utilizzati solo all’interno dell’applicazione. Non richiede uno spazio dei nomi.
Best practices nella progettazione del payload
Includere solo i dati necessari
JWT è una stringa codificata in Base64, quindi la dimensione del token influisce sull’efficienza della comunicazione. Assicurati di includere solo i dati essenziali per l’autenticazione e l’autorizzazione.
{
"sub": "1234567890",
"name": "John Doe",
"roles": ["admin", "user"]
}
Considerare la sensibilità dei dati
JWT è protetto dalla firma, ma non è crittografato. Evita di includere informazioni sensibili come:
- Password
- Informazioni sulle carte di credito
- Informazioni personali identificabili (PII: Personally Identifiable Information)
Impostare scadenze chiare per i token
Impostare sempre il claim exp
per limitare la durata del token e ridurre i rischi di sicurezza.
Esempio di progettazione del payload di JWT
Ecco un esempio pratico di payload di JWT:
{
"iss": "https://example.com", // Emittente
"sub": "user123", // ID utente
"aud": "https://myapi.example.com", // Destinatario
"exp": 1701209952, // Scadenza (timestamp UNIX)
"iat": 1701206352, // Ora di emissione
"roles": ["user", "admin"], // Permessi utente
"preferences": {
"theme": "dark", // Attributi personalizzati
"notifications": true
}
}
Considerazioni sulla dimensione del token
Se il payload è troppo grande, la dimensione complessiva del token aumenta e potrebbero sorgere problemi come:
- Aumento delle dimensioni della richiesta HTTP se inviata tramite intestazioni
- Aumento dell’utilizzo dei dati nei dispositivi mobili
Riduci la dimensione del token eliminando i claim non necessari.
Riassunto
La progettazione del payload è cruciale per garantire l’efficienza e la sicurezza del token. Utilizzare i claim standard e includere solo le informazioni necessarie per la tua applicazione ti aiuterà a progettare il token ottimale.
Firma con chiave segreta e pubblica
La firma è un elemento cruciale per la sicurezza di JWT. In particolare, l’uso di una firma con chiave pubblica e privata (firma asimmetrica) è efficace per prevenire le manomissioni e garantire l’affidabilità. In questa sezione, spiegheremo come funziona la firma e come implementarla in Python.
Funzionamento della firma
Esistono due tipi di firma in JWT:
- Firma simmetrica (HMAC)
- Utilizza una chiave segreta condivisa per firmare e verificare.
- Semplice e veloce, ma la gestione della chiave può essere problematica.
- Algoritmi utilizzati: HS256, HS512
- Firma asimmetrica (RSA, ECDSA)
- La firma viene effettuata con la chiave privata e verificata con la chiave pubblica.
- Adatta per la comunicazione tra server o con entità terze.
- Algoritmi utilizzati: RS256, ES256
Vantaggi della firma asimmetrica:
- Se la chiave privata è ben protetta, la sicurezza è garantita anche se la chiave pubblica viene compromessa.
- Adatta quando ci sono più validatori.
Implementazione della firma RSA in Python
Di seguito un esempio di come generare e verificare un JWT con firma RSA usando la libreria PyJWT in Python.
Preparazione delle chiavi
Genera prima la chiave privata e quella pubblica.
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
Generazione del JWT
Genera un JWT utilizzando la chiave privata.
import jwt
import datetime
# Caricamento della chiave privata
with open("private.pem", "r") as key_file:
private_key = key_file.read()
# Creazione del payload
payload = {
"sub": "1234567890",
"name": "John Doe",
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# Generazione del JWT (firmato)
token = jwt.encode(payload, private_key, algorithm="RS256")
print("JWT generato:", token)
Verifica del JWT
Verifica il JWT utilizzando la chiave pubblica.
# Caricamento della chiave pubblica
with open("public.pem", "r") as key_file:
public_key = key_file.read()
# Verifica del JWT
try:
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
print("Payload decodificato:", decoded)
except jwt.ExpiredSignatureError:
print("Il token è scaduto.")
except jwt.InvalidTokenError:
print("Il token non è valido.")
Criteri di scelta dell’algoritmo di firma
- HS256 (HMAC): per sistemi di autenticazione semplici lato server
- RS256 (RSA): ideale per comunicazioni tra server o integrazione con sistemi esterni
- ES256 (ECDSA): utile quando si richiede una firma con chiave pubblica più leggera
Best Practices
- Gestione sicura delle chiavi segrete
- Proteggi rigorosamente la chiave segreta e limita l’accesso ad essa.
- Impostazione della scadenza
- Usa il claim
exp
per impostare una scadenza breve per il token.
- Scelta esplicita dell’algoritmo
- Specifica sempre esplicitamente l’algoritmo di firma e non fare affidamento su valori predefiniti.
Riassunto
La firma con chiavi segrete e pubbliche è essenziale per garantire la sicurezza di JWT. La firma RSA è particolarmente utile per comunicazioni tra server e sistemi distribuiti. Implementa le best practices per costruire un sistema di autenticazione sicuro e affidabile usando Python.
Processo di verifica di JWT
Il processo di verifica è fondamentale per confermare la validità di un JWT durante l’autenticazione. Verifica che il token non sia stato manomesso, che sia firmato con la chiave corretta e che sia ancora valido. In questa sezione, esploreremo i passaggi per la verifica di JWT in Python.
Processo base di verifica di JWT
- Verifica della firma
- Controlla che il token sia stato firmato correttamente con la chiave privata o pubblica. Se la firma non corrisponde, il token è invalido.
- Controllo della scadenza
- Verifica il claim
exp
per assicurarti che il token sia ancora valido. I token scaduti sono invalidi.
- Verifica di altri claims
- Verifica claims come
iss
(emittente) eaud
(destinatario) per garantire che il token sia utilizzato correttamente.
Verifica di JWT in Python
Qui sotto mostriamo un esempio di come verificare un JWT usando PyJWT in Python.
Verifica base
import jwt
# Chiave segreta o pubblica
SECRET_KEY = "your-secret-key"
# Token da verificare
token = "your.jwt.token"
# Decodifica e verifica del JWT
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print("Payload verificato:", decoded)
except jwt.ExpiredSignatureError:
print("Il token è scaduto.")
except jwt.InvalidTokenError:
print("Il token non è valido.")
Verifica con RS256 (chiave pubblica)
Per una firma asimmetrica (RS256), la verifica avviene utilizzando la chiave pubblica.
# Caricamento della chiave pubblica
with open("public.pem", "r") as key_file:
public_key = key_file.read()
# Decodifica e verifica del JWT
try:
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
print("Payload verificato:", decoded)
except jwt.ExpiredSignatureError:
print("Il token è scaduto.")
except jwt.InvalidTokenError:
print("Il token non è valido.")
Verifica di claims aggiuntivi
È possibile rafforzare la verifica specificando claims aggiuntivi, come mostrato nel seguente esempio:
decoded = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"verify_exp": True}, # Verifica scadenza
audience="https://myapi.example.com", # Verifica destinatario
issuer="https://example.com" # Verifica emittente
)
Considerazioni sulla verifica
- Specifica dell’algoritmo
- Assicurati di specificare sempre l’algoritmo utilizzato durante la verifica del token per evitare rischi di sicurezza.
- Gestione dei token scaduti
- I token scaduti (
ExpiredSignatureError
) devono essere gestiti chiedendo all’utente di effettuare nuovamente l’accesso o di aggiornare il token.
- Verifica della fonte del token
- Assicurati che il token provenga da una fonte affidabile.
Flusso di verifica del token
- Ricevi il token
- Verifica la struttura del token (Header, Payload, Signature)
- Controlla l’algoritmo nel header e verifica la firma
- Verifica il claim
exp
eiss
nel payload - Se la verifica ha successo, autorizza la richiesta
Riassunto
La verifica di JWT è essenziale nel processo di autenticazione. Verifica la firma con la chiave corretta e controlla la scadenza per garantire un sistema sicuro e affidabile. Implementa il flusso di verifica del token in Python per un’autenticazione robusta.
Gestione della scadenza del token e del refresh
Quando si utilizzano i JWT, è fondamentale gestire correttamente la scadenza e implementare i refresh token per aumentare la sicurezza. In questa sezione, vediamo come impostare la scadenza e implementare il refresh dei token in Python.
Impostazione della scadenza
Puoi impostare la scadenza del token utilizzando il claim exp
. Ecco un esempio di implementazione in Python:
import jwt
import datetime
# Impostazione della chiave segreta
SECRET_KEY = "your-secret-key"
# Aggiunta della scadenza al payload
payload = {
"sub": "1234567890", # ID utente
"name": "John Doe", # Informazioni utente
"iat": datetime.datetime.utcnow(), # Ora di emissione
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # 30 minuti di validità
}
# Generazione del token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("Access Token:", token)
Considerazioni importanti
- Imposta una scadenza breve (5-30 minuti) per ridurre i rischi in caso di furto del token.
- Se il token è scaduto, implementa un sistema di reautenticazione o refresh del token.
Progettazione del refresh token
Il refresh token viene emesso separatamente dall’access token e viene utilizzato per ottenere un nuovo access token quando quest’ultimo scade. Caratteristiche del refresh token:
- Ha una durata più lunga (da giorni a settimane)
- Di solito viene gestito dal server
Esempio di emessione del refresh token
# Payload del refresh token
refresh_payload = {
"sub": "1234567890", # ID utente
"iat": datetime.datetime.utcnow(), # Ora di emissione
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=7) # 7 giorni di validità
}
# Generazione del refresh token
refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm="HS256")
print("Refresh Token:", refresh_token)
Implementazione del processo di refresh
Quando il token scade, utilizza il refresh token per generare un nuovo access token.
# Decodifica del refresh token e generazione di un nuovo access token
try:
decoded_refresh = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
# Generazione del nuovo access token
new_access_payload = {
"sub": decoded_refresh["sub"],
"name": "John Doe",
"iat": datetime.datetime.utcnow(),
"exp
": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
new_access_token = jwt.encode(new_access_payload, SECRET_KEY, algorithm="HS256")
print("Nuovo Access Token:", new_access_token)
except jwt.ExpiredSignatureError:
print("Il refresh token è scaduto.")
except jwt.InvalidTokenError:
print("Il refresh token non è valido.")
Considerazioni di sicurezza per il refresh token
- Gestione lato server
- I refresh token devono essere conservati in modo sicuro sul server per prevenire il furto o la manomissione.
- Uso una tantum
- I refresh token devono essere invalidati una volta utilizzati per prevenire attacchi di tipo session hijacking.
- Richiesta di reautenticazione
- Richiedi all’utente di effettuare una nuova autenticazione al momento dell’uso del refresh token per aumentare la sicurezza.
Esempio di progettazione dell’API per il refresh token
Ecco un esempio di progettazione dell’API per il rinnovo del token:
- Il client invia un access token scaduto e un refresh token.
- Il server verifica il refresh token.
- Se valido, il server emette un nuovo access token e lo restituisce al client.
Esempio di endpoint API
POST /api/token/refresh
Authorization: Bearer {refresh_token}
Esempio di risposta:
{
"access_token": "new_access_token",
"expires_in": 1800
}
Riassunto
Impostare correttamente la scadenza dei token e implementare il refresh token è fondamentale per garantire la sicurezza e migliorare l’esperienza utente. L’uso combinato di scadenze brevi e refresh token ti permetterà di costruire un sistema di autenticazione sicuro e affidabile.
Rischi di sicurezza e relative contromisure
JWT è un ottimo strumento per l’autenticazione e la gestione delle sessioni, ma ci sono rischi di sicurezza se non implementato correttamente. In questa sezione, discuteremo i rischi più comuni legati ai JWT e le misure per mitigarli.
Rischi di sicurezza comuni
1. Compromissione della chiave segreta
Se la chiave segreta di JWT viene compromessa, è possibile generare token falsificati e accedere al sistema.
Contromisure:
- Conserva la chiave segreta in un ambiente sicuro (es. variabili di ambiente, servizi di gestione delle chiavi).
- Ruota regolarmente la chiave segreta.
2. Manomissione del token
Se la firma del token non viene verificata correttamente, è possibile che il contenuto venga manomesso.
Contromisure:
- Verifica sempre la firma del token.
- Specifica esplicitamente l’algoritmo e non consentire algoritmi non sicuri (ad esempio
none
).
3. Furto del token
Se un token viene rubato, l’attaccante può utilizzarlo per accedere come se fosse un utente legittimo.
Contromisure:
- Usa HTTPS per criptare le comunicazioni.
- Conserva i token in modo sicuro (es. cookie HttpOnly).
- Imposta una scadenza breve per i token.
4. Attacco di tipo replay
Se un token viene rubato, può essere riutilizzato da un attaccante per prendere il controllo della sessione.
Contromisure:
- Aggiungi un identificatore unico (
jti
) per ogni token e traccia l’uso del token lato server. - Inserisci i token utilizzati in una blacklist.
5. Difficoltà di invalidazione
Poiché i JWT sono stateless, non è sempre facile invalidare un token una volta emesso.
Contromisure:
- Usa refresh token e blacklist per implementare l’invalidazione.
- Imposta una scadenza breve per i token e aggiorna frequentemente.
- Prevenzione del Cross-Site Scripting (XSS)
Usa cookie HttpOnly per proteggere i token. - Monitoraggio dei log
Registra i log delle emissioni e verifiche dei token per rilevare utilizzi sospetti.
Implementazione di contromisure specifiche in Python
1. Gestione delle chiavi
Un esempio di recupero della chiave segreta da variabili di ambiente:
import os
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
if not SECRET_KEY:
raise ValueError("La chiave segreta deve essere impostata!")
2. Specifica esplicita dell’algoritmo
Per prevenire algoritmi non sicuri, specifica sempre l’algoritmo di firma:
import jwt
# Verifica del token
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
3. Utilizzo di HTTPS
Forza HTTPS in Flask:
from flask import Flask
app = Flask(__name__)
app.config['PREFERRED_URL_SCHEME'] = 'https'
4. Prevenzione degli attacchi di replay
Aggiungi un identificatore unico (jti
) per tracciare l’uso del token:
import uuid
payload = {
"sub": "1234567890",
"jti": str(uuid.uuid4()), # ID unico
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
5. Implementazione di blacklist
Un esempio di invalidazione di un token usando Redis:
import redis
# Client Redis
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
# Invalidazione del token
redis_client.setex("blacklist:" + token, 1800, "true") # Invalidazione dopo 1800 secondi
Altri suggerimenti
Riassunto
JWT offre numerosi vantaggi, ma richiede misure di sicurezza adeguate. Proteggi le chiavi segrete, verifica le firme dei token e imposta scadenze appropriate per garantire un sistema di autenticazione sicuro.
Riassunto
In questo articolo abbiamo esplorato come generare e verificare JWT in Python, spiegando concetti di base, implementazioni e misure di sicurezza. JWT è uno strumento potente per costruire sistemi di autenticazione scalabili e senza stato, ma è fondamentale tenere conto della gestione delle chiavi, della scadenza dei token e della progettazione dei refresh token per garantire la sicurezza del sistema.
Utilizza le librerie Python per costruire un sistema sicuro ed efficiente che garantisca l’autenticazione degli utenti e la gestione affidabile delle sessioni.