Mastering Flask: A Comprehensive Web Development Series for Python Enthusiasts - Flask Sessions

Mastering Flask: A Comprehensive Web Development Series for Python Enthusiasts - Flask Sessions

Article 5: Sessions

Be Sure to Complete the Series

In this Flask web development series, we'll dive deeper into Flask and cover topics that build on the basics introduced in the first article. We'll explore more advanced features of Flask, including database integration, user authentication, and RESTful APIs. We'll also show you how to use Docker to containerize your Flask application, simplify deployment, and set up CI/CD with GitHub Actions to automate testing and deployment. By following this series, you'll gain a comprehensive understanding of Flask and its ecosystem, and easily build robust, scalable web applications. So follow the entire series to get the most out of your Flask learning journey.

Learning Outcomes

After reading an article on Flask sessions, learners will gain knowledge on how to use Flask sessions to manage user sessions. By the end of the article, learners will be able to create a secure and robust web application that utilizes Flask sessions.

If you feel stuck please check the github repo to get some help.

Link: github.com/ritwikmath/Mastering-Flask/tree/..

Session

A session is a system that retains user data across multiple requests. When a session is created, a session key is generated and stored as a cookie in the user's browser. This session key is then transmitted to the server with each subsequent request to identify the session information. To establish a session, a developer must define a secret key. This secret key is utilized to encrypt the session key using cryptography, rendering it unalterable in the front-end by anyone who does not possess the secret key.

Problem Statement

The problem at hand is how to store user information in a session after they have successfully logged in, and subsequently use this session to verify the user's credentials and prevent unauthorized access to restricted pages.

Set the Secret Key

It is recommended to generate a secret key that is as random as possible. Various methods exist in each operating system to create secure secret keys for Flask. The Flask attribute secret_key must be assigned the randomly generated value. If the secret key is modified, any sessions established prior to the change will become invalid. Attempting to access the session object using the prior keys will result in a null value.

app.py

if __name__ == '__main__':
    Mongo().connect()
    from route.logs import log_bp
    from route.developers import developer_bp
    from route.home import home_bp
    from route.auth import auth_bp
    app.register_blueprint(log_bp)
    app.register_blueprint(developer_bp)
    app.register_blueprint(home_bp)
    app.register_blueprint(auth_bp)
    app.secret_key = '8d31312aa6efba09119e9ecb1246e32ba5f6fd862a90bc107298d809d902cbce'
    app.run(debug=True)

Login

The process of utilizing sessions is straightforward and uncomplicated. Firstly, import the session object from the Flask module. Once all the necessary verifications have been performed, assign the authenticated user to a key-value pair within the session object.

route/auth.py

from flask import Blueprint, request, redirect, url_for, flash, session
***
@auth_bp.post('/login')
def login():
    try:
        developer = Mongo().db.developers.find_one({
            'email': request.form['email']
        })
        if not developer:
            raise NotFound('Email is not registred')
        match = check_password_hash(developer.get('password'), request.form['password'])
        if not match:
            raise BadRequest('Password did not match')
        del developer['password']
        session['loggedin_user'] = json.loads(json.dumps(developer, default=str))
        return redirect(url_for('home.dashboard'))
    except Exception as ex:
        flash(ex.__str__())
        return redirect(url_for('home.loginForm'))

The code json.loads(json.dumps(developer, default=str)) converts a Python object, developer, into a JSON formatted string using json.dumps() function. The default=str argument in json.dumps() specifies that non-serializable values (such as ObjectId, datetime objects) should be converted to a string representation, using the str() function.

After a successful login, Flask generates a new key called loggedin_user. On the front-end, the session key can be located within the cookies, under the name session.

Templates

Flask templates can access the session object, which is readily available in Jinja2 by default. In this context, a welcome message is displayed to the logged-in user, and the name attribute of the loggedin_user key will be fetched from the session.

templates/dashboard.html

<body>
    <form method="post" action="{{url_for('auth.logout')}}" class="form">
      <input type="submit" class="button" value="Logout">
    </form>
    <h1>Dashboard</h1>
    <h1>Welcome {{session['loggedin_user']['name']}}</h1>
    <div class="card-container">
        {% for log in logs[3::-1] %}
            <div class="card">
                {% for key, value in log.items() %}
                    <h4>{{key}}: </h4>
                    <p>{{value}}</p>
                {% endfor %}
            </div>
        {% endfor %}
    </div>
</body>

Welcome {{session['loggedin_user']['name']}} is a Jinja2 template expression that displays a welcome message to the logged-in user, where session is the session object containing user information and loggedin_user is the key generated by Flask to store the logged-in user's information.

Restricting Access

To prevent unauthorized access, a user must be logged in to access the dashboard. This is achieved by checking whether the session contains a loggedin_user key for that particular user. If the key is not present, the user is redirected to the login page.

if not session.get('loggedin_user'):
        return redirect(url_for('home.loginForm'))

If the loggedin_user key is not present in the session object, it will return a null value. By applying the not operator to this expression, it will evaluate to True. Flask will recognize this as a failure to authenticate the user and redirect them back to the login form.

For login and register pages, users can only access them if they are not already logged in. In this case, the not operator is not used. If the loggedin_user key exists in the session object, the user is redirected to the dashboard page instead of being allowed to access the login or register pages.

if session.get('loggedin_user'):
        return redirect(request.referrer or url_for('home.dashboard'))

return redirect(request.referrer or url_for('home.dashboard')) is a Flask function call that redirects the user to the previous page they were on, or to the dashboard page if there is no previous page.

The request.referrer returns the URL of the page that sent the request to the current page. If there is a previous page, request.referrer will have a value, and the user will be redirected to that page. If there is no previous page (i.e., request.referrer is None), the url_for('home.dashboard') function generates the URL for the dashboard page, and the user is redirected there instead.

The redirect() function call is used to perform the actual redirect.

route/home.py

from flask import Blueprint, render_template, url_for, session, redirect, request
from database.mongo import Database as Mongo
import json

home_bp = Blueprint('home', __name__)

@home_bp.get('/')
def dashboard():
    if not session.get('loggedin_user'):
        return redirect(url_for('home.loginForm'))
    logs = list(Mongo().db.logs.find({}))
    return render_template('dashboard.html', logs=json.loads(json.dumps(logs, default=str)))

@home_bp.get('/login')
def loginForm():
    if session.get('loggedin_user'):
        return redirect(request.referrer or url_for('home.dashboard'))
    return render_template('login.html')

@home_bp.get('/register')
def registerForm():
    if session.get('loggedin_user'):
        return redirect(request.referrer or url_for('home.dashboard'))
    return render_template('register.html')

Conclusion

In conclusion, Flask sessions provide a convenient way to manage user sessions in web applications. By implementing user authentication and session management with Flask, developers can create a secure and robust application that provides a personalized experience for their users. Additionally, the use of form validation with WTForms ensures that user input is secure and valid, further improving the application's security. With the knowledge gained from this article, developers can confidently create web applications that utilize Flask sessions, user authentication, and form validation to provide a seamless and secure user experience.