Web Development Using Flask - Part 2

In the first part I covered the basics of a Flask application, routing, using templates, and file structure. In this part I will cover more advanced topics, like: using session for handling user authentication (for these I will cover Python annotations/decorators), using flash messages to provide feedback for the user, and how to setup and use custom error pages.

Python decorators/annotations

Python and many other programming languages have the concept of decorators -- also called annotations. In Python, decorators are basically functions, which receive a function as a parameter.

I created a decorator called user_login_needed:

# decorator for Flask application methods
def user_login_needed(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        is_user_logged_in = session['user_logged_in']
        if not is_user_logged_in:
            return redirect(url_for('login'))
        else:
            return f(*args, **kwargs)
    return decorated_function

The code at first sight looks more complicated than it actually is. I defined the user_login_needed() function which receives the f function parameter. The @wraps is a convenience function which invokes the update_wrapper() method from Python’s functools library. The @wraps(f) is very expressive, since it really wraps the f function parameter with the method it annotates, decorated_function(). The decorated_function() is a method defined inside the user_login_needed() method. The decorated_function() redirects the request to the login page if the user is not logged in, OTHERWISE it invokes the f function parameter, passing in all the parameters – this is where all the magic happens. Using decorators, we basically insert a new layer between the invocation and execution of a method.

The annotation is used at the login method:

@app.route("/")
@user_login_needed
def index():
    return render_template("index.html")

When the Flask app applies the routing rule and executes the index() method, it executes this line of code (simplified):

# no extra parameters passed since index does not take any parameters
return user_login_needed(index) 

Creating login/logout for user authentication

Nowadays there is no Web application where the user does not need to sign in or register. For this purpose I created the login() and logout() methods. The decorator @user_login_needed was created to protect routes of the application from an unauthenticated user. All we need to do is annotate the methods which we want to protect with authentication.

Login

I added a new route and a new template for creating the login functionality.

Login Page

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    elif request.method == "POST":
        user_name = request.form['username']
        password = request.form['password']
        if is_user_valid(user_name, password):
            session['user_logged_in'] = True 
            error_message = "User {} successfuly logged in.".format(user_name)
            flash(error_message)
            print(error_message)
            return redirect(url_for("index"))
        else:
            print("Invalid user.")
            flash("Invalid user/password!")
            return redirect(url_for("login"))
    else:
        error_message = "Invalid request method:{}".format(request.method)
        print(error_message)
        flash(error_message)
        return redirect(url_for("login"))

    
def is_user_valid(user_name, password):
    return user_name == "john" and password == "1234"

I configured the http://localhost:5000/login route to only accept GET and POST HTTP requests, in any other case (for ex. PUT or DELETE requests) I redirect the user to the login page. If the request method is GET, then I simply return the rendered login.html page. In case the request method is POST (meaning the user pressed the Login button) I authenticate the user using the is_user_valid() method. If the user is valid I set the user_logged_in variable in the session to True – remember the user_login_needed decorator looks for this variable in the session. (NOTE: User validation and authentication should be done with proper encryption and respect for some basic software security principles and policies. Using the above code (is_user_valid method) for user authentication is insecure and should NOT be used in a real/production-like environment!)

Logout

The code for logout is much simpler and shorter:

@app.route("/logout", methods=["GET"])
def logout():
    session['user_logged_in'] = False
    print("User logout successful.")
    flash("Logout successful") 
    return redirect(url_for("login"))

The logout can be accessed only using GET requests, it sets the user_logged_in session variable to False, prints a debug message, pushes a flash messages to the template engine and returns the login page.

Flash messages

Flash messages provide a good way to send relevant information about what is happening on the webpage to the users. Flask provides a message system, which is accessed by the Jinja2 template engine. More details are on the help page.

Flushing messages can be done using the flush() method, as this can be seen in the logout() method’s code. The flushed messages are displayed in the login.html page, these can be accessed using the get_flashed_messages() method.

{% for message in get_flashed_messages() %}
   <p class="login_message"> {{ message }} </p>
{% endfor %}

Flask Flush Messages on Login page

Custom Error Handlers and Error Pages

Flask has the possibility to set up custom error handlers for errors. Setting these up is easy and can also be done using decorators:

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

I created the method page_not_found() and specified to Flask using the @app.errorhandler(404), that in case there comes a request with an invalid route, it should execute the page_not_found() method. Inside the method I am returning an HTML page, but setting the HTTP Status Code to 404 – meaning the requested page was Not Found. More information on this topic can be found at Redirects and Errors help page.

The application is on GitHub at https://github.com/gergob/flask_web. The readme will help to install dependencies and start the application. 

Greg Bogdan
Greg Bogdan Hire Me

Software Engineer, Blogger, Tech Enthusiast

I am a Software Engineer with over 7 years of experience in different domains(ERP, Financial Products and Alerting Systems). My main expertise is .NET, Java, Python and JavaScript. I like technical writing and have good experience in creating tutorials and how to technical articles. I am passionate about technology and I love what I do and I always intend to 100% fulfill the project which I am ...

Hire Me

Next Article

3 EU markets you should be looking at