Flask (parte 6): authentication
Nella parte precedente abbiamo accennato alla gestione delle password utente.
Ora vediamo come gestire l’autenticazione sul sito di uno User e la User-session.
Dobbiamo installare per prima cosa l’estensione Flask-Login:
(venv) >pip install flask-login
Poi, siccome l’inserimento delle proprie credenziali da parte dello User che si connette o
si vuole registrare, necessita di Forms, installiamo anche l’estensione WTF:
(venv) >pip install flask-wtf
Ricordiamoci, dopo un’installazione, di rinfrescare i requirements con il comando:
(venv) >pip freeze > requirements.txt
Siccome abbiamo già accennato ai Blueprints e siccome vogliamo tenere i vari codici separati,
creeremo un blueprint specifico che chiameremo “auth”, che gestirà tutto quello
che concerne l’autenticazione dello User, nell afattispecie forms e views.
“auth” avrà di diritto le proprie route specifiche e verrà inizializzato e gestito,
come abbiamo fatto per il blueprint “main”.
Creiamo la dir app/auth ed il file costruttore app/auth/__init__.py
from flask import Blueprint auth = Blueprint('auth', __name__) from . import views
ora nella factory function dell’applicazione (file app/__init__.py)
aggiungiamo il blueprint ed inizializziamo le estensioni precedentemente installate:
from flask import Flask, render_template from flask.ext.bootstrap import Bootstrap from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.login import LoginManager from config import config bootstrap = Bootstrap() db = SQLAlchemy() login_manager = LoginManager() # inizializzo l'estensione login_manager.session_protection = 'strong' login_manager.login_view = 'auth.login' def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) config[config_name].init_app(app) bootstrap.init_app(app) db.init_app(app) login_manager.init_app(app) from main import main as main_blueprint app.register_blueprint(main_blueprint) from auth import auth as auth_blueprint # importo e registro... app.register_blueprint(auth_blueprint) # ...il blueprint auth return app
In fase di inizializzazione dell’estensione che gestisce il login, setto
il livello di protezione della user-session a “strong”, ma è possibile
utilizzare anche i valori None e “basic”.
#... login_manager = LoginManager() login_manager.session_protection = 'strong' #...
Come già accennato, utilizzando un blueprint dedicato, tutte le views e tutti i forms relativi
all’autenticazione di uno User, risiederanno nella dir app/auth, dobbiamo quindi esplicitamente
settare l’endpoint della view di login in fase di inizializzazione della estensione,
tramite l’attributo login_view.
#... login_manager.login_view = 'auth.login' #...
Per prima cosa creiamo il form che si occuperà del login nel file app/auth/forms.py:
from flask.ext.wtf import Form from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import Required, Email class LoginForm(Form): email = StringField('Email', validators=[Required(), Length(1, 64), Email()]) password = PasswordField('Password', validators=[Required()]) remember_me = BooleanField('Keep me logged in') submit = SubmitField('Log In')
Come si nota, ci viene fornito tutto dall’estensione wtf precedentemente installata.
Abbiamo 2 field dove inseriamo email e pw, un booleanfield (casellina di spunta) e
il bottone submit. I primi due campi vengono validati tramite l’argomento validators.
Ora creiamo la view con la route dedicata ed importiamo in essa la form appena creata.
Il file app/auth/views.py sarà:
from flask import render_template, redirect, request, url_for, flash from flask.ext.login import login_user from . import auth from ..models import User from .forms import LoginForm # qui importo la form di login! @auth.route('/login', methods=['GET', 'POST']) # route dedicata al login def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user is not None and user.verify_password(form.password.data): login_user(user, form.remember_me.data) return redirect(request.args.get('next') or url_for('main.index')) flash('Invalid username or password.') return render_template('auth/login.html', form=form)
Il funzionamento è semplicissimo:
accedendo all’url auth/login, abbiamo una richiesta (request) di tipo GET, quindi
passiamo direttamente alla rappresentazione della template auth/login.html, saltando
tutto il blocco if, che tratta solo una richiesta di tipo POST grazie al metodo
validate_on_submit() che restituisce True solo alla pressione del tasto Log-in.
In questo caso viene cercato lo user tramite query in base alle credenziali inserite e,
se trovato (not None), lo user e la sessione di competenza, vengono memorizzati e
reindirizzati nella pagina index, altrimenti protetta.
Se lo User non viene trovato appare un messaggio sulla pagina di Login grazie a flash.
La funzione login_user() mantiene l’utente loggato per tutta la sessione. Il booleanfield
remember_me mantiene l’utente loggato anche in caso di chiusura della finestra browser, in caso
di valore True, altrimenti “muore” in caso di chiusura del browser ed è necessario un nuovo login.
Per gestire le templates del blueprint auth, creiamo una sottodirectory auth in templates e creiamo
il file login.html (app/templates/auth/login.html):
{% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %}Fantamanager - Login{% endblock %} {% block page_content %} <div class="page-header"> <h1>Login</h1> </div> <div class="col-md-4"> {{ wtf.quick_form(form) }} </div> {% endblock %}
Facciamo anche un’utile modifica al file app/templates/base.html
aggiungendo i links sign-in e sign-out nella navigation bar:
{% extends "bootstrap/base.html" %} {% block title %}Fantamanager{% endblock %} {% block head %} {{ super() }} <link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}" type="image/x-icon"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}"> {% endblock %} {% block navbar %} <div class="navbar navbar-inverse" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/users">Users</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> {% if current_user.is_authenticated() %} <li><a href="{{ url_for('main.user', username=current_user.username) }}">Profile</a></li> {% endif %} </ul> <ul class="nav navbar-nav navbar-right"> {% if current_user.is_authenticated() %} <li><a href="{{ url_for('auth.logout') }}">Sign Out</a></li> {% else %} <li><a href="{{ url_for('auth.login') }}">Sign In</a></li> {% endif %} </ul> </div> </div> </div> {% endblock %} {% block content %} <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-warning"> <button type="button" class="close" data-dismiss="alert"> × </button> {{ message }} </div> {% endfor %} {% block page_content %}{% endblock %} </div> {% endblock %}
La variabile current_user è fornita e gestita direttamente da Flask-Login ed è pertanto
disponibile nelle views e nelle templates.
Le template specifiche poi, potranno estendere direttamente base.html invece di bootstrap/base.html
Per ottenere vantaggio da questo dobbiamo però ereditare la nostra classe User,
da quella predefinita di Flask-Login.
La classe User del file app/models.py sarà pertanto:
# ... from flask.ext.login import UserMixin class User(UserMixin, db.Model): # ...
Perchè tutto funzioni, Flask-Login richiede di settare una callback che,
dato un ID, ritorni lo User corrispondente (app/models.py):
from . import login_manager #... @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))
Ora non resta che proteggere una route. Per farlo basta utilizzare il decoratore
@login_required ad es. (app/main/views.py)
#... from flask.ext.login import login_required @main.route('/', methods=['GET']) @login_required def index(): return render_template('index.html') @main.route('/users', methods=['GET']) @login_required def users(): #...
Abbiamo fatto tutto per gestire il login, ma manca ancora il logout.
Apriamo il file app/auth/views.py ed aggiungiamo la view logout
#... from flask.ext.login import logout_user, login_required #... @auth.route('/logout') @login_required def logout(): logout_user() flash('You have been logged out.') return redirect(url_for('main.index'))
la funzione logout_user, serve per resettare e rimuovere la user-session corrente.
Fino ad ora, abbiamo aggiunto Users tramite shell, quindi il prossimo passo è la
registrazione di un nuovo utente tramite form, con conseguente invio di richiesta
conferma tramite email.
articolo successivo:
Flask (parte 7): mail
articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
Flask (parte 3): database
Flask (parte 4): views e templates
Flask (parte 5): password security
link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)
Commenti recenti