Authored in connection with the 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 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 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 and initialize the application like so:
git clone https://github.com/LordGhostX/fauna-auth-demo
cd fauna-auth-demo/
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:
cd user-interface
pip install -r requirements.txt
Finally, run the application to make sure it’s working. In your terminal, type:
python3 app.py
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.
In the dashboard, click on the NEW DATABASE
button, provide a name for your database then press the SAVE
button.
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.
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.
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.
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.
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:
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:
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.
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:
client.query(
q.create(
q.collection("users"), {
"credentials": {"password": "secret password"},
"data": {"email": "test@test.com"}
}
)
)
The code above makes a 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:
@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")
Note: You can only create good user profiles by providing the
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 function.
Here is a sample Python code that demonstrates this functionality:
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 of the account is retrieved using the index we created earlier for the users
collection and the 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:
@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")
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 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:
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:
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:
@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 function for validating sessions earlier. The response contains the Fauna Ref of the user, which you can use with Get to retrieve the collection data.
Here is a sample Python code that demonstrates this functionality:
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:
@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:
<h5>Welcome {{ user_details.data.fullname }} 👋</h5>
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:
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:
@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:
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:
@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)
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. I also created a Github Gist that shows Fauna's user identity capabilities using Python. If you have any questions, don't hesitate to contact me on Twitter: @LordGhostX
Top comments (0)