
L'autenticazione è un componente cruciale per qualsiasi applicazione web moderna. Quando si sviluppa un'API RESTful, una delle tecniche più popolari ed efficaci per gestire l'autenticazione degli utenti è l'utilizzo di JSON Web Tokens (JWT). Questo approccio non solo semplifica la gestione delle sessioni, ma consente anche di costruire applicazioni scalabili e sicure, in cui non è necessario mantenere lo stato lato server. In questo articolo, esploreremo come implementare un sistema di autenticazione basato su JWT in un'applicazione web realizzata con Flask.
Un JSON Web Token (JWT) è un metodo di rappresentazione sicura e compatta delle informazioni, utile per l'autenticazione tra il client e il server. La struttura di un JWT è composta da tre sezioni: l'intestazione (header), il payload e la firma. L'intestazione contiene informazioni sul tipo di token e sull'algoritmo di firma utilizzato. Il payload contiene i dati effettivi che vogliamo trasmettere, come l'ID dell'utente. La firma serve per garantire che il token non venga alterato durante il suo percorso. La grande vantaggio di JWT risiede nel fatto che sono auto-contenuti, il che significa che tutte le informazioni necessarie per l'autenticazione sono incluse nel token stesso, evitando la necessità di memorizzare sessioni sul server.
Creazione dell'applicazione Flask
Per cominciare, installiamo Flask e altre librerie necessarie per lavorare con JWT, come pyjwt
e flask_sqlalchemy
. La libreria flask_sqlalchemy
ci permette di gestire facilmente il database, mentre pyjwt
sarà utilizzata per generare e validare i JWT.
Per installare le librerie, esegui il comando:
pip install Flask pyjwt flask_sqlalchemy
Iniziamo a configurare la nostra applicazione Flask. Creiamo un file app.py
e definiremo una semplice applicazione Flask con un modello di database per gli utenti. Ecco come possiamo fare:
from flask import Flask, request, jsonify
import jwt
import datetime
from functools import wraps
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key' # Cambia con una chiave segreta
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Modello User per la nostra app
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(50), nullable=False)
def __repr__(self):
return f"User('{self.username}')"
# Crea il database
with app.app_context():
db.create_all()
if __name__ == "__main__":
app.run(debug=True)
Abbiamo configurato un'applicazione di base in Flask con un database SQLite. Il modello User
è stato definito per memorizzare gli utenti con un campo username
e password
. La chiave segreta SECRET_KEY
è utilizzata per firmare il token JWT, ed è importante cambiarla con una chiave unica e sicura per la tua applicazione.
Registrazione di un nuovo utente
La registrazione di un nuovo utente è il primo passo verso l'autenticazione. Creiamo una route /register
che accetta una richiesta POST per registrare un utente. Il nome utente e la password vengono presi dal corpo della richiesta, quindi verifichiamo se l'utente esiste già nel database. Se l'utente non esiste, lo creiamo e memorizziamo nel database. Ecco come:
@app.route('/register', methods=['POST'])
def register():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'message': 'Username and password are required'}), 400
# Verifica se l'utente esiste già
if User.query.filter_by(username=username).first():
return jsonify({'message': 'User already exists'}), 400
# Crea un nuovo utente
new_user = User(username=username, password=password)
db.session.add(new_user)
db.session.commit()
return jsonify({'message': 'User created successfully'}), 201
In questo codice, la funzione register
esegue le seguenti operazioni:
- Estrae i dati dal corpo della richiesta.
- Verifica se il nome utente e la password sono forniti.
- Controlla se l'utente esiste già nel database.
- Se l'utente non esiste, viene creato e salvato nel database.
Autenticazione con JWT
Ora che abbiamo un sistema di registrazione, possiamo implementare il login degli utenti. La route /login
restituirà un JWT quando le credenziali dell'utente sono corrette. Questo è fondamentale perché il JWT rappresenta l'autenticazione dell'utente e sarà utilizzato per accedere a endpoint protetti. Quando l'utente inserisce il nome utente e la password, Flask verifica se queste corrispondono ai dati nel database. Se sono corretti, viene generato un JWT che contiene l'ID dell'utente e la scadenza del token. Ecco come implementare il login:
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'message': 'Username and password are required'}), 400
user = User.query.filter_by(username=username).first()
# Verifica se l'utente esiste e la password è corretta
if not user or user.password != password:
return jsonify({'message': 'Invalid credentials'}), 401
# Creazione del JWT
token = jwt.encode({
'user_id': user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({'token': token}), 200
In questa funzione:
- Controlliamo se l'utente esiste e se la password è corretta.
- Se le credenziali sono valide, generiamo un token JWT.
- Il token contiene l'ID dell'utente e una scadenza di un'ora. La scadenza è importante per limitare la durata di validità del token.
Proteggere gli endpoint con JWT
Una delle caratteristiche principali di JWT è che possiamo usarlo per proteggere gli endpoint della nostra applicazione. Creiamo un decoratore token_required
, che verificherà che ogni richiesta a un endpoint protetto contenga un token valido. Se il token non è presente o è invalido, verrà restituito un errore. Ecco come:
def token_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'Token is missing!'}), 403
try:
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
current_user = User.query.get(data['user_id'])
except Exception as e:
return jsonify({'message': 'Token is invalid!'}), 403
return f(current_user, *args, **kwargs)
return decorated_function
Abbiamo definito il decoratore token_required
che estrae il token dall'intestazione Authorization
della richiesta, lo decodifica e verifica la sua validità. Se il token è valido, prosegue con l'esecuzione della funzione, passando l'utente corrente come parametro. Se il token è mancante o invalido, viene restituito un errore.
Ora, possiamo applicare questo decoratore a qualsiasi endpoint che desideriamo proteggere, come ad esempio una route che restituisce i dettagli dell'utente:
@app.route('/profile', methods=['GET'])
@token_required
def profile(current_user):
return jsonify({'username': current_user.username}), 200
In questa route, l'utente dovrà fornire un token valido per accedere alle informazioni del proprio profilo.
Conclusione
Abbiamo visto come implementare un sistema di autenticazione sicuro con JWT in un'applicazione Flask. L'approccio basato su JWT è particolarmente utile per le applicazioni moderne che richiedono una gestione dell'autenticazione senza sessione, poiché consente di gestire l'accesso in modo stateless, senza la necessità di memorizzare sessioni sul server. Inoltre, il sistema di scadenza del token garantisce una maggiore sicurezza. Con la protezione degli endpoint tramite il decoratore token_required
, l'applicazione è ora pronta per gestire in modo sicuro gli utenti autenticati.