📜 ⬆️ ⬇️

Mega-Tutorial Flask, Part 5: User Login

Preface from the translator.
The translation of the previous parts of this manual was done by wiygn . With his consent, I continue this business.

This is the fifth article in the series where I describe my experience of writing a Python web application using the Flask mic framework.



The purpose of this guide is to develop a fairly functional microblog application, which I decided to call microblog, in the absence of originality.


')


Brief repetition


In the previous part, we created a database and learned how to fill it with users and posts, but this functionality has not yet been implemented in our application. Two chapters ago we learned how to create web forms and created a form for authorization.



In this article, we will combine our knowledge of web forms and databases and write our system to log users. At the end of this guide, our small application will register new users and authorize them.



To work with this chapter, your application must be what we left at the end of the previous chapter. Please make sure the application is installed and running.




Configuration


As in the previous chapters, we will start by customizing the extensions that we will use. For authorization, we need two extensions - Flask-Login and Flask-OpenID. Configure them as follows ( app/__init__.py ) :



 import os from flask.ext.login import LoginManager from flask.ext.openid import OpenID from config import basedir lm = LoginManager() lm.init_app(app) oid = OpenID(app, os.path.join(basedir, 'tmp')) 

The Flask-OpenID extension needs to store its temporary files somewhere, for this, during initialization, the path to the tmp folder is passed to it.



Authorization view function


Let's update our view function (file app/views.py ):



 from flask import render_template, flash, redirect, session, url_for, request, g from flask.ext.login import login_user, logout_user, current_user, login_required from app import app, db, lm, oid from forms import LoginForm from models import User, ROLE_USER, ROLE_ADMIN @app.route('/login', methods = ['GET', 'POST']) @oid.loginhandler def login(): if g.user is not None and g.user.is_authenticated(): return redirect(url_for('index')) form = LoginForm() if form.validate_on_submit(): session['remember_me'] = form.remember_me.data return oid.try_login(form.openid.data, ask_for = ['nickname', 'email']) return render_template('login.html', title = 'Sign In', form = form, providers = app.config['OPENID_PROVIDERS']) 


Please note that we have imported several new modules, some of which will be used later.



There are some differences from the previous version. We have added a new decorator for the display function. Thanks to oid.loginhandler Flask-OpenID now knows that this is a feature for authorization.



g is a global Flask object for storing and exchanging data during the life of a query. That is where we will store data about the current user. At the top of the function body, we check the value of g.user . If the user is already authorized, we redirect him to the main page. It makes no sense to try again to authorize in this case.



The url_for function that we used when calling redirect provides the ability to get the URL for the name of the view function passed to it. Of course, you can use redirect('/index') , but there are some very good reasons to entrust the construction of a URL to a function specifically designed for this.



We also updated the code that processes the data obtained from the authorization form. Here we do two things. First, we save the value of the remember_me field in the Flask session (not to be confused with db.session - the session provided by the Flask-SQLAlchemy extension). As mentioned above, flask.g can store data only during the life of the request. While flask.session is a more complex repository. The data stored in the session will also be available during all subsequent requests from one client . Information is stored until explicitly deleted. This behavior is possible due to the fact that Flask stores separate sessions for each client.



Calling oid.try_login starts the authorization process using Flask-OpenID. This function takes two arguments: openid , obtained from a web form and a list of fields that we would like to receive from an OpenID provider. Since our User model has the nickname and email attributes, this is the data we will request.



OpenID authentication is asynchronous. If a positive response is received from the provider, Flask-OpenID will invoke the function declared by the decorator oid.after_login . Otherwise, the user will return to the login page again.



Handling a response from an OpenID provider


This is how the implementation of the after_login function after_login ( app/views.py ) :



 @oid.after_login def after_login(resp): if resp.email is None or resp.email == "": flash('Invalid login. Please try again.') return redirect(url_for('login')) user = User.query.filter_by(email = resp.email).first() if user is None: nickname = resp.nickname if nickname is None or nickname == "": nickname = resp.email.split('@')[0] user = User(nickname = nickname, email = resp.email, role = ROLE_USER) db.session.add(user) db.session.commit() remember_me = False if 'remember_me' in session: remember_me = session['remember_me'] session.pop('remember_me', None) login_user(user, remember = remember_me) return redirect(request.args.get('next') or url_for('index')) 


The resp argument passed to the after_login function contains the data received from the OpenID provider.



First of all, we need to check that the response from the server contains the user's email, otherwise we cannot authorize it. Check if the received email is in our database. If nothing is found, add a new user to the database. It is worth noting that some OpenID providers do not provide a nickname , but for us this is not a problem, we can use the name from the mail.



After that, we try to get the remember_me value from the Flask session, this is the same value that we saved in the login view function.



Then we call the login_user function from the Flask-Login module to finally authorize the user in our application.



In the end, we redirect the user to the address given in the next attribute, or to the main page if there is no such parameter in the request. The idea of ​​the next parameter is quite simple. Suppose you want to make some pages available for viewing only to authorized users. Using Flask-Login, such pages can be designated using the login_required decorator. If an anonymous user tries to open such a page, he will be automatically redirected to the authorization page, while Flask-Login will save the URL original page in the next parameter. We will only need to send the user to this address after the authorization is completed.



In order for Flask-Login to know where to send users for authorization, we must inform it about this during initialization (file app/__init__.py ) :



 lm = LoginManager() lm.init_app(app) lm.login_view = 'login' 


Global object g.user


In the login view, we checked the status of g.user , in order to determine if the current user is already authorized. For this to work, we use the Flask before_request event. All functions declared with the before_request decorator will be run immediately before calling the mapping function each time a request is received. Thus, it is quite logical to set the value of g.user here ( app/views.py ) :



 @app.before_request def before_request(): g.user = current_user 


That's all we need. Flask-Login gives us access to the current_user variable, we simply copy a reference to this value in g , for ease of further use. Now the current user will be available everywhere, even inside templates.



Display home page


In the previous chapter, we used stub objects in the index function, since we did not yet have real users and posts. Now we have users, it's time to use it:



 @app.route('/') @app.route('/index') @login_required def index(): user = g.user posts = [ { 'author': { 'nickname': 'John' }, 'body': 'Beautiful day in Portland!' }, { 'author': { 'nickname': 'Susan' }, 'body': 'The Avengers movie was so cool!' } ] return render_template('index.html', title = 'Home', user = user, posts = posts) 


We made only two changes to this feature. First, the login_required decorator was added. From now on we can be sure that only registered users will see this page.



Secondly, we transfer to the template directly the g.user object instead of the stub used earlier.



It's time to start the application.



When you go to http://localhost:5000 , you will see a login page instead of the main page. Authorization using OpenID takes place using the URL provided by the provider. In order not to enter the address manually, you can use one of the links below the text box.



During the authorization process, you will be redirected to the provider's website, where you will need to log in and give your permission to transfer some information to your application. In this case, only those parameters that we requested will be transmitted, i.e. mail address and nickname. No private information, including passwords, is reported by OpenID providers.



After that, you will find yourself on the main page, now as an authorized user.



You can also experiment with the remember_me flag. If you turn it on, you will remain logged in even after you close the browser and open it again.



Sign Out


We implemented the login, it's time to add the ability to log out. This is done very simply (file app/views.py ) :



 @app.route('/logout') def logout(): logout_user() return redirect(url_for('index')) 


In addition, we need to add the appropriate link to the template. Place it at the top of the page, next to other navigation links (file app/templates/base.html ) :


 <html> <head> {% if title %} <title>{{title}} - microblog</title> {% else %} <title>microblog</title> {% endif %} </head> <body> <div>Microblog: <a href="{{ url_for('index') }}">Home</a> {% if g.user.is_authenticated() %} | <a href="{{ url_for('logout') }}">Logout</a> {% endif %} </div> <hr> {% with messages = get_flashed_messages() %} {% if messages %} <ul> {% for message in messages %} <li>{{ message }} </li> {% endfor %} </ul> {% endif %} {% endwith %} {% block content %}{% endblock %} </body> </html> 


See how easy it is! We just need to check the contents of g.user , and if the current user is authorized, add a link to exit. Do not forget that instead of direct addresses it is better to use url_for in such cases ..



Final words


Now we have a complete system for user authorization. In the next chapter, we will create a user profile page, and add the ability to use avatars.



To save time, you can use the link and download the application code, which includes all the changes from this article:
Download microblog-0.5.zip




See you again!



Miguel

Source: https://habr.com/ru/post/222983/


All Articles