Home > flask, python > Flask (parte 6): authentication

Flask (parte 6): authentication

9 Luglio 2015

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"> &times; </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)

Categorie:flask, python Tag: ,
I commenti sono chiusi.