Managing User Authentication and Sessions with Fauna and Flask
Tags: fauna, webdev, security, beginners
Published: 2021-03-25 17:41:28

*Authored in connection with the [Write With Fauna](https://fauna.com/blog/write-with-fauna) program.*

Managing user authentication and sessions is a task you are bound to encounter when building web applications. You need to authenticate users on your platform and know if they have permission to access your servers. However, this task is not easy. This is why many third-party products are willing to help you get started.

In this article, you will be introduced to the concept of authentication, authorization, user identity, and session management. I will also discuss the benefits of handling user authentication with [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_Auth_SEsenyi) and then integrate Fauna’s built-in user authentication and session management capabilities into a Flask application.

# Prerequisites

To follow and fully understand this tutorial, you will need to have:

* Python 3.6 or a newer version.
* A text editor.
* An understanding of Fauna and Flask.

# Introduction

## Authentication vs. Authorization

Authentication (AuthN) validates that users are who they claim to be. This could be achieved by using one-time pins (OTP) or passwords when logging in to websites.

Authorization (AuthZ) is the process of giving a user permission to access a specific resource or function in a system. For example, only logged-in users can process their eCommerce shopping carts.

## User Identity vs. User Session

User identity refers to a unique identifier for people who use an application. For example, **"John Smith"** and **"John Doe"** are entirely different people because their unique identifiers (full names) are not the same. In web applications, users are uniquely identifiable using information like usernames and email addresses.

A user session is the series of interactions and responses between a user and a web server, e.g., users logging-in to their dashboard and browsing around. It is also the information exchange between devices communicating with one another. Sessions help to manage specific user states, user identities, authorization, and many other interactions.

## Fauna Serverless Database

[Fauna](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_Auth_SEsenyi) is a flexible, developer-friendly, transactional cloud database delivered as a secure Data API. It provides built-in user authentication and session management capabilities, making their integration into web applications seamless.

# Downloading the Demo Application

In this tutorial, you will be implementing user authentication and session management with Fauna into a dashboard with registration and login pages.

## Clone the Demo App Repository

For the sake of convenience, I have written a Flask application with a Bootstrap user interface that we will use in this article. To get started, you will need to clone [my repository](https://github.com/LordGhostX/fauna-auth-demo) and initialize the application like so:

```bash
git clone https://github.com/LordGhostX/fauna-auth-demo  
cd fauna-auth-demo/
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j07pn8p12p2pg3tmzgw7.png)

The `user-interface` directory contains only the demo’s user interface, while the `fauna-integration` directory contains the entire application (Fauna + Flask) that you will build in this tutorial.

## Install the Demo Requirements

You need to install the external libraries required by the demo application before you can run it. In your terminal, type:

```bash
cd user-interface  
pip install -r requirements.txt
```

Finally, run the application to make sure it’s working. In your terminal, type:

```bash
python3 app.py
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npwtjllx5va5pt9i2egg.png "running app in terminal")

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/glsk2b04s67t9zkt9rpr.png "web preview of demo")

# Setting Up the Fauna Database

## Create the Fauna Database

You need to create the database for the demo application in Fauna’s dashboard. If you have not created an account on Fauna before now, create one on [Fauna’s website](https://dashboard.fauna.com/accounts/register?utm_source=DevTo&utm_medium=referral&utm_campaign=WritewithFauna_Auth_SEsenyi).

In the dashboard, click on the `NEW DATABASE` button, provide a name for your database then press the `SAVE` button.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pk445r987r1fpib4dpbq.png "creating database")

## Create the Database Collections

Now, you need to create a Fauna collection to store data collected in the database you just created. A collection is similar to SQL tables containing data with similar characteristics, e.g., a user collection with information about users in the database.

To create a collection, navigate to the `Collections` tab on the Fauna sidebar (left side of the screen), click on the `NEW COLLECTION` button, provide a name for the collection you want to create then press the `SAVE` button.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3mawc6di7rw9rvvgu0i.png "creating collections")

## Create the Collection Indexes

You need to create an index for the collection of the database. A Fauna index allows you to browse through data stored in a database collection based on specific attributes.

To create an index, move to the `Indexes` tab on the Fauna sidebar (left side of the screen), click the `NEW INDEX` button, provide the necessary information, and press the `SAVE` button.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9dk1u39o2k9xfoagsj2.png "creating indexes")

## Generate Database Security Key

Finally, you need to create a `Security Key` to connect your database to the demo application. Go to the `Security` tab on the Fauna sidebar (left side of the screen), click the `NEW KEY` button, provide the necessary information, and then press the `SAVE` button.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svy3mv68nh43lcb3y84a.png "creating fauna security key")

Once you have done this, Fauna will present you with your `Secret Key`. You should copy the key as soon as Fauna generates it and store it somewhere easily retrievable because Fauna will only show this once.

# Integrating Fauna’s User Authentication

Now that you have set up the base demo application and your Fauna database let’s move to integrate Fauna’s built-in authentication feature as shown in the [documentation](https://docs.fauna.com/fauna/current/tutorials/authentication/user).

## Install Fauna’s Python Driver

You need to get the Python driver for Fauna, which extends Python programs’ functionality by providing functions that allow them to make queries to Fauna databases. It’s available on pip, and can be installed with a single command in your terminal.

In the terminal, type:

```bash
pip install faunadb
```

## Importing Fauna into Your Demo

You need to import the Fauna driver into your Flask application to use it in the current project. To do this, add the following block of code to the `app.py` file:

```python
from functools import wraps  
from faunadb import query as q  
from faunadb.objects import Ref  
from faunadb.client import FaunaClient  
from faunadb.errors import BadRequest, Unauthorized
```

You also have to initialize the Python driver client with your database `Security Key` so you can make queries to your database from your application. Add the following block of code in your `app.py` file right after initializing your Flask app.

```python
app.config["SECRET_KEY"] = "APP_SECRET_KEY"  
client = FaunaClient(secret="YOUR-SECRET-HERE")
```

> Don’t forget to replace the secret keys' placeholders with the appropriate values for your application.

## Creating User Profiles in Fauna

To create new user profiles that Fauna will help manage authentication and sessions, you need to create a new user document in your collection that contains the user’s email address and password.

Here is a sample Python code that demonstrates this functionality:

```python
client.query(
    q.create(
        q.collection("users"), {
            "credentials": {"password": "secret password"},
            "data": {"email": "test@test.com"}
        }
    )
)
```

The code above makes a [Create](https://docs.fauna.com/fauna/current/api/fql/functions/create) query to your Fauna database. It creates a collection entry with the email address provided in the `data` parameter and password provided in the `credentials` parameter. Passing the `credentials` parameter is the key to creating user profiles in Fauna. Fauna can now use any entry with this parameter for user authentication.

You can integrate this into your application by updating the `register` route with the code below:

```python
@app.route("/register/", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        email = request.form.get("email").strip().lower()
        fullname = request.form.get("fullname").strip()
        password = request.form.get("password")

        try:
            result = client.query(
                q.create(
                    q.collection("users"), {
                        "credentials": {"password": password},
                        "data": {
                            "email": email,
                            "fullname": fullname
                        }
                    }
                )
            )
        except BadRequest as e:
            flash("The account you are trying to create already exists!", "danger")
            return redirect(url_for("register"))

        flash(
            "You have successfully created your account, you can proceed to login!", "success")
        return redirect(url_for("login"))

    return render_template("register.html")
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8dtjekl6sqcszw5qtz9n.png "registration successful")

> Note: You can only create good user profiles by providing the `email` and `password` fields, and Fauna does not store credentials in plain text but uses a BCrypt hash of the password provided.

## Authenticating Users with Fauna

To authenticate stored user profiles with Fauna, you need to provide the user’s email address and password, then use the [Login](https://docs.fauna.com/fauna/current/api/fql/functions/login) function.

Here is a sample Python code that demonstrates this functionality:

```python
client.query(
    q.login(
        q.match(q.index("users_by_email"), "test@test.com"), {
            "password": "secret password"}
    )
)
```

The code above uses the `Login` built-in function of Fauna to authenticate a user profile’s reference with the password provided. The [Ref](https://docs.fauna.com/fauna/current/api/fql/functions/ref) of the account is retrieved using the index we created earlier for the `users` collection and the [Match](https://docs.fauna.com/fauna/current/api/fql/functions/match) function in Fauna.

> When you run the `Login` query, Fauna will provide you a secret token in the response that you use to make authorized requests for that user. Each time the query executes, you will get different values for the secret token. These values are to be stored after receiving them.

To integrate this block of code into our application, update the `login` route in the `app.py` file with the code below:

```python
@app.route("/login/", methods=["GET", "POST"])
def login():
    if "user_secret" in session:
        return redirect(url_for("dashboard"))

    if request.method == "POST":
        email = request.form.get("email").strip().lower()
        password = request.form.get("password")

        try:
            result = client.query(
                q.login(
                    q.match(q.index("users_by_email"), email), {
                        "password": password}
                )
            )
        except BadRequest as e:
            flash(
                "You have supplied invalid login credentials, please try again!", "danger")
            return redirect(url_for("login"))

        session["user_secret"] = result["secret"]
        return redirect(url_for("dashboard"))

    return render_template("login.html")
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itvc3fp5ixe1p64fazcj.png "failed authentication")

> Note: The image above shows the result of supplying invalid login credentials to the demo application.

# Integrating Fauna’s Session Management

## Validating User Sessions with Fauna

Validating user sessions means verifying that a particular session token is valid (authentic) before using it. Validation helps tokens expire based on conditions like reaching an expiry date or when a user logs out of a device.

To validate user sessions with Fauna, you need to make an authorized request with the built-in [CurrentIdentity](https://docs.fauna.com/fauna/current/api/fql/functions/currentidentity) function to your database. The `CurrentIdentity` function returns information of the user associated with a session token.

Here is a sample Python code that demonstrates this functionality:

```bash
client = FaunaClient(secret="secret token")
client.query(
    q.current_identity()
)
```

To integrate this block of code into our application, we need to create a Python wrapper that will perform the validation and protect routes that require the AuthN & AuthZ e.g. dashboard route. Add the following block of code to the `app.py` file:

```python
def login_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if "user_secret" in session:
            try:
                user_client = FaunaClient(secret=session["user_secret"])
                result = user_client.query(
                    q.current_identity()
                )
            except Unauthorized as e:
                session.clear()
                return redirect(url_for("login"))
        else:
            return redirect(url_for("login"))
        return f(result, *args, **kwargs)

    return decorated
```

You also need to protect the `dashboard` route with the newly created decorator so only authorized visitors can access it. Update the `dashboard` route with the code below:

```python
@app.route("/dashboard/", methods=["GET", "POST"])
@login_required
def dashboard(user):
```

## Fetching User Info with Sessions

To fetch users’ information with their sessions using Fauna, you need to use the same [CurrentIdentity](https://docs.fauna.com/fauna/current/api/fql/functions/currentidentity) function for validating sessions earlier. The response contains the [Fauna Ref](https://docs.fauna.com/fauna/current/api/fql/functions/ref) of the user, which you can use with [Get](https://docs.fauna.com/fauna/current/api/fql/functions/get) to retrieve the collection data.

Here is a sample Python code that demonstrates this functionality:

```python
client.query(
    q.get(
        q.ref(q.collection("users"), "user ref ID")
    )
)
```

To integrate this block of code into our application, update the `dashboard` route in the `app.py` file with the code below:

```python
@app.route("/dashboard/", methods=["GET", "POST"])
@login_required
def dashboard(user):
    user_details = client.query(
        q.get(
            q.ref(q.collection("users"), user.id())
        )
    )

    return render_template("dashboard.html", user_details=user_details)
```

You also need to update the dashboard template to render the data retrieved from your Fauna using Jinja. Update the welcome line in the `dashboard.html` file located in the `templates` directory with the code below:

```html
<h5>Welcome {{ user_details.data.fullname }} 👋</h5>
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo26z3r6ebxu59vkenox.png "dashboard preview")

## Deleting User Sessions with Fauna

Deleting user sessions means making specific user sessions unusable. This comes in handy when you want users to log out of their accounts.

To delete user sessions with Fauna, you need to make an authorized request with your database’s built-in Logout function. The `Logout` function deletes session tokens associated with a user profile.

Here is a sample Python code that demonstrates this functionality:

```python
client = FaunaClient(secret="secret token")
result = client.query(
    q.logout(True)
)
```

> The `Logout` function takes a boolean parameter that tells Fauna to delete all session tokens associated with a user profile (True) or only the currently passed token (False).

To integrate this block of code into our application, update the `logout` route in the `app.py` file with the code below:

```python
@app.route("/dashboard/logout/<string:logout_type>/")
@login_required
def logout(user, logout_type):
    if logout_type == "all":
        all_tokens = True
    else:
        all_tokens = False

    user_client = FaunaClient(secret=session["user_secret"])
    result = user_client.query(
        q.logout(all_tokens)
    )
    session.clear()

    return redirect(url_for("index"))
```

# Extra AuthN Functionalities

## Changing User Password with Fauna

To change the password of stored user profiles with Fauna, you need to update your user collection, passing the request’s new credentials.

Here is a sample Python code that demonstrates this functionality:

```python
client.query(
    q.update(
        q.ref(q.collection("users"), "user ref ID"), {
            "credentials": {"password": "new secret password"}
        }
    )
)
```

The code above uses the `Update` built-in function of Fauna to change a user profile’s credentials to the new password provided.

To integrate this block of code into our application, update the `dashboard` route in the `app.py` file with the code below:

```python
@app.route("/dashboard/", methods=["GET", "POST"])
@login_required
def dashboard(user):
    if request.method == "POST":
        old_password = request.form.get("old-password").strip()
        new_password = request.form.get("new-password").strip()

        try:
            result = client.query(
                q.identify(
                    q.ref(q.collection("users"), user.id()), old_password
                )
            )

            if not result:
                raise Exception()

            result = client.query(
                q.update(
                    q.ref(q.collection("users"), user.id()), {
                        "credentials": {"password": new_password}
                    }
                )
            )
        except Exception as e:
            flash(
                "You have supplied an invalid password!", "danger")
            return redirect(url_for("dashboard"))

        flash(
            "You have successfully changed your account password!", "success")
        return redirect(url_for("dashboard"))

    user_details = client.query(
        q.get(
            q.ref(q.collection("users"), user.id())
        )
    )

    return render_template("dashboard.html", user_details=user_details)
```

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onegcf8fh6x1ncnvivii.png)

# Conclusion

In this article, you learned the concept of authentication, authorization, user identity, session management, benefits of handling user authentication with Fauna, and integrated Fauna's built-in AuthN & AuthZ features into a Flask application.

The source code of the demo application is available on [GitHub](https://github.com/LordGhostX/fauna-auth-demo). I also created a [Github Gist](https://gist.github.com/LordGhostX/9d70357f9803904506939ab6f4bb3a9a) that shows Fauna's user identity capabilities using Python. If you have any questions, don't hesitate to contact me on Twitter: [@LordGhostX](https://twitter.com/LordGhostX)