Autorisation et authentification

Utilisation de l’extension flask-login

L’un des moyens les plus simples d’implémenter un système d’autorisation consiste à utiliser l’extension [flask-login][1]. Le site Web du projet contient un guide de démarrage rapide détaillé et bien rédigé, dont une version plus courte est disponible dans cet exemple.

Idée générale

L’extension expose un ensemble de fonctions utilisées pour :

  • connecter les utilisateurs
  • déconnecter les utilisateurs
  • vérifier si un utilisateur est connecté ou non et découvrir de quel utilisateur il s’agit

Ce qu’il ne fait pas et ce que vous devez faire vous-même :

  • ne fournit pas de moyen de stocker les utilisateurs, par exemple dans la base de données
  • ne fournit pas un moyen de vérifier les informations d’identification de l’utilisateur, par exemple le nom d’utilisateur et le mot de passe

Vous trouverez ci-dessous un ensemble minimal d’étapes nécessaires pour que tout fonctionne.

** Je recommanderais de placer tout le code lié à l’authentification dans un module ou un package séparé, par exemple auth.py. De cette façon, vous pouvez créer séparément les classes, objets ou fonctions personnalisées nécessaires.**

Créer un LoginManager

L’extension utilise une classe [LoginManager][2] qui doit être enregistrée sur votre objet d’application [Flask][3].

from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app) # app is a Flask object

Comme mentionné précédemment, LoginManager peut par exemple être une variable globale dans un fichier ou un package séparé. Ensuite, il peut être importé dans le fichier dans lequel l’objet Flask ​​est créé ou dans la fonction d’usine de votre application et initialisé.

Spécifiez un rappel utilisé pour charger les utilisateurs

Un utilisateur sera normalement chargé à partir d’une base de données. Le rappel doit renvoyer un objet qui représente un utilisateur correspondant à l’ID fourni. Il doit renvoyer “Aucun” si l’ID n’est pas valide.

@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id) # Fetch the user from the database

Cela peut être fait directement en dessous de la création de votre LoginManager.

Une classe représentant votre utilisateur

Comme mentionné, le rappel user_loader doit renvoyer un objet qui représente un utilisateur. Qu’est-ce que cela signifie exactement? Cet objet peut par exemple être un wrapper autour d’objets utilisateur stockés dans votre base de données ou simplement directement un modèle de votre base de données. Cet objet doit implémenter les méthodes et propriétés suivantes. Cela signifie que si le rappel renvoie votre modèle de base de données, vous devez vous assurer que les propriétés et méthodes mentionnées sont ajoutées à votre modèle.

  • is_authenticated

    This property should return True if the user is authenticated, i.e. they have provided valid credentials. You will want to ensure that the objects which represent your users returned by the user_loader callback return True for that method.

  • is_active

    This property should return True if this is an active user - in addition to being authenticated, they also have activated their account, not been suspended, or any condition your application has for rejecting an account. Inactive accounts may not log in. If you don’t have such a mechanism present return True from this method.

  • est_anonyme

    This property should return True if this is an anonymous user. That means that your user object returned by the user_loader callback should return True.

  • get_id()

    This method must return a unicode that uniquely identifies this user, and can be used to load the user from the user_loader callback. Note that this must be a unicode - if the ID is natively an int or some other type, you will need to convert it to unicode. If the user_loader callback returns objects from the database this method will most likely return the database ID of this particular user. The same ID should of course cause the user_loader callback to return the same user later on.

Si vous voulez vous faciliter la tâche (**c’est en fait recommandé), vous pouvez hériter de [UserMixin][4] dans l’objet renvoyé par le rappel user_loader (vraisemblablement un modèle de base de données). Vous pouvez voir comment ces méthodes et propriétés sont implémentées par défaut dans ce mixin [ici][5].

Connexion des utilisateurs

L’extension vous laisse la validation du nom d’utilisateur et du mot de passe saisis par l’utilisateur. En fait, l’extension ne se soucie pas de savoir si vous utilisez un combo nom d’utilisateur et mot de passe ou un autre mécanisme. Il s’agit d’un exemple de connexion des utilisateurs à l’aide d’un nom d’utilisateur et d’un mot de passe.

@app.route('/login', methods=['GET', 'POST'])
def login():
    # Here we use a class of some kind to represent and validate our
    # client-side form data. For example, WTForms is a library that will
    # handle this for us, and we use a custom LoginForm to validate.
    form = LoginForm()
    if form.validate_on_submit():
        # Login and validate the user.
        # user should be an instance of your `User` class
        login_user(user)

        flask.flash('Logged in successfully.')

        next = flask.request.args.get('next')
        # is_safe_url should check if the url is safe for redirects.
        # See http://flask.pocoo.org/snippets/62/ for an example.
        if not is_safe_url(next):
            return flask.abort(400)

        return flask.redirect(next or flask.url_for('index'))
    return flask.render_template('login.html', form=form)

En général, la connexion des utilisateurs s’effectue en appelant [login_user][6] et en lui transmettant une instance d’un objet représentant votre utilisateur mentionné précédemment. Comme indiqué, cela se produit généralement après avoir récupéré l’utilisateur de la base de données et validé ses informations d’identification, mais l’objet utilisateur apparaît comme par magie dans cet exemple.

J’ai connecté un utilisateur, et maintenant ?

L’objet renvoyé par le rappel user_loader est accessible de plusieurs manières.

  • Dans les modèles :

    The extension automatically injects it under the name current_user using a template context processor. To disable that behaviour and use your custom processor set add_context_processor=False in your LoginManager constructor.

      {% if current_user.is_authenticated %}
        Hi {{ current_user.name }}!
      {% endif %}
    
  • En code Python :

    The extension provides a request-bound object called [current_user][7].

      from flask_login import current_user    
    
      @app.route("/hello")
      def hello():
          # Assuming that there is a name property on your user object
          # returned by the callback
          if current_user.is_authenticated:
              return 'Hello %s!' % current_user.name 
          else:
              return 'You are not logged in!'
    
  • Limiter rapidement l’accès à l’aide d’un décorateur Un décorateur [login_required][8] peut être utilisé pour limiter l’accès rapidement.

      from flask_login import login_required
    
      @app.route("/settings")
      @login_required
      def settings():
          pass
    

Déconnexion des utilisateurs

Les utilisateurs peuvent être déconnectés en appelant [logout_user()][9]. Il semble qu’il est sûr de le faire même si l’utilisateur n’est pas connecté, de sorte que le décorateur @login_required peut très probablement être omis.

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(somewhere)

Que se passe-t-il si un utilisateur n’est pas connecté et que j’accède à l’objet current_user ?

Par défaut, un [AnonymousUserMixin][10] est renvoyé :

  • is_active et is_authenticated sont False
  • is_anonymous est Vrai
  • get_id() renvoie Aucun

Pour utiliser un objet différent pour les utilisateurs anonymes, fournissez un appelable (une classe ou une fonction d’usine) qui crée des utilisateurs anonymes dans votre LoginManager avec :

login_manager.anonymous_user = MyAnonymousUser

Et ensuite ?

Ceci conclut l’introduction de base à l’extension. Pour en savoir plus sur la configuration et les options supplémentaires, il est fortement recommandé de [lire le guide officiel][1].

[1] : https://flask-login.readthedocs.io/en/latest/ [2] : https://flask-login.readthedocs.io/en/latest/#flask_login.LoginManager [3] : http://flask.pocoo.org/docs/dev/api/#application-object [4] : https://flask-login.readthedocs.io/en/latest/#flask_login.UserMixin [5] : https://flask-login.readthedocs.io/en/latest/_modules/flask_login/mixins.html#UserMixin [6] : https://flask-login.readthedocs.io/en/latest/#flask_login.login_user [7] : https://flask-login.readthedocs.io/en/latest/#flask_login.current_user [8] : https://flask-login.readthedocs.io/en/latest/#flask_login.login_required [9] : https://flask-login.readthedocs.io/en/latest/#flask_login.logout_user [10] : https://flask-login.readthedocs.io/en/latest/#flask_login.AnonymousUserMixin

Expiration de la session de connexion

C’est une bonne pratique d’expirer la session connectée après un temps spécifique, vous pouvez y parvenir avec Flask-Login.

from flask import Flask, session
from datetime import timedelta
from flask_login import LoginManager, login_require, login_user, logout_user

# Create Flask application

app = Flask(__name__) 

# Define Flask-login configuration 

login_mgr = LoginManager(app)
login_mgr.login_view = 'login'
login_mgr.refresh_view = 'relogin'
login_mgr.needs_refresh_message = (u"Session timedout, please re-login")
login_mgr.needs_refresh_message_category = "info"


@app.before_request
def before_request():
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=5)

La durée de vie par défaut de la session est de 31 jours, l’utilisateur doit spécifier la vue d’actualisation de la connexion en cas d’expiration du délai.

app.permanent_session_lifetime = timedelta(minutes=5)

La ligne ci-dessus obligera l’utilisateur à se reconnecter toutes les 5 minutes.