Introduction
In this article, I will be showing how to build a user login system with Python, Flask, and Fauna. Flask is a Python microframework that is used to build web applications while Fauna is an easy-to-use NoSQL serverless database.
Fauna provides a lot of features to make its database integration process seamless. I have come to love its native support for GraphQL and cloud functions. You can create an account on Fauna here.
App Structure
In this article, we will be building a web application with 4 pages:
- Home/Landing Page
- Dashboard Page
- Sign-in Page
- Signup Page
To begin, create a folder and create the following files app.py
, templates/dashboard.html
, templates/home.html
, templates/signin.html
, templates/signup.html
, .env
. Your project structure should look like the following:
Installing Dependencies
Next, you need to install the required dependencies, type the following in your terminal:
pip install flask faunadb python-dotenv
pip freeze > requirements.txt
Creating Our Flask App
To create a simple Flask application for the tutorial, type the code below in the app.py
from flask import Flask
app = Flask(__name__)
@app.route(‘/’)
def home():
return {
'Message': 'Welcome to our app'
}
if __name__ == '__main__':
app.run()
To run the code above, first set the FLASK_DEBUG
variable to true
then run the app using the command below:
export FLASK_DEBUG=true # if you are using windows change export to set
flask run
You should get a response similar to the image below in your terminal:
Click on the link and you will see the following in your browser
Yaay! you have built a simple flask app with a few lines of code.
Setting Up Fauna Database
Step 1: Creating Fauna Database
To create a database, go to Fauna's Dashboard, then click on NEW DATABASE
and enter the database name in the prompt
Step 2: Creating Fauna Secret Key
We will be using Fauna’s secret key to communicate with our database. To generate a secret key, click on the security button and click on NEW KEY
button in the security tab.
Copy your secret key and keep it safe, you won’t be able to see it again.
Step 3: Creating Our Fauna Collection
Click on NEW COLLECTION
and enter the collection name in the prompt, in this case, my collection name is user
.
Step 4: Creating An Index For Your Collection
Go to the INDEXES
tab in the left part of the screen and click on the NEW INDEX
button, and enter your details in the prompt, it should look like the one in the image below
Building Our Application
Let’s get started with the real application for this article, our app will demonstrate how to authenticate users in a Flask application.
In your .env file, type the following:
Step 1: Adding Environment Keys
FAUNA_SECRET='your fauna secret'
SECRET_KEY='your application secret key'
Step 2: Importing Dependencies and Defining Home Route
Let’s write the view for the home route, update your app.py
file with the code below:
import re
from flask import Flask, flash, redirect, url_for, render_template, request, session
from werkzeug.security import generate_password_hash, check_password_hash
from faunadb import query as q
from faunadb.client import FaunaClient
from faunadb.objects import Ref
from faunadb.errors import BadRequest, NotFound
from dotenv import load_dotenv
import os, secrets
app = Flask(__name__)
app.config['SECRET_KEY']=os.getenv('SECRET_KEY')
client = FaunaClient(secret=os.getenv('FAUNA_SECRET'))
@app.route('/')
def home():
return render_template('home.html')
Next, we create our home.html
file and add the following:
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fauna-Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header left">
<a class="navbar-brand active" href="/">Fauna-Login</a>
</div>
<ul class="nav navbar-nav right">
<li><a href="/signin">SignIn</a></li>
<li><a href="/signup">Signup</a></li>
</ul>
</div>
</nav>
<!-- Page Content -->
<div>
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<h1>Hello there!</h1>
</body>
</html>
The with
block is a feature of jinja
which is the Flask template engine. The with
block here is used for flashing messages to the user like signup success message, error message by the user, etc. In the server, we will use the flash function in Flask, and if that code block is seen it will display it in the browser because we have added it to our HTML file.
If you refresh the page you should see the image below. Note that the login and register buttons aren’t working yet as we have not defined routes for them yet.
Step 3: Defining Signin and Signup routes
Let’s define our SignIn and SignUp endpoint, add the following code below the home function in the app.py
file
@app.route('/signin/', methods=['POST', 'GET'])
def signin():
if session.get('user_id'):
flash('You are logged in!', 'warning')
return redirect(url_for('dashboard'))
if request.method =='POST':
# get the user details
email = request.form['email']
password = request.form['password']
# verify if the user details exist
try:
user = client.query(
q.get(q.match(q.index('user_by_email'), email))
)
except NotFound:
flash('Invalid email or password', category='warning')
else:
if check_password_hash(user['data']['password'], password):
session['user_id'] = user['ref'].id()
flash('Signed in successfully', 'success')
return redirect(url_for('dashboard'))
else:
flash('Invalid email or password', 'warning')
return render_template('signin.html')
@app.route('/signup/', methods=['POST', 'GET'])
def signup():
if session.get('user_id'):
flash('You are logged in!', 'warning')
return redirect(url_for('dashboard'))
if request.method =='POST':
name = request.form['name']
email = request.form['email']
password = request.form['password']
email_regex = '^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$'
if not re.search(email_regex, email) or not 6 < len(password) < 20:
flash('Invalid email or password!, password needs to be between 6 and 20 characters', 'warning')
return render_template('signup.html')
if password != request.form['confirm_password']:
flash('password fields not equal', 'warning')
return render_template('signup.html')
password = generate_password_hash(password)
user = {'name': name, 'email': email, 'password': password}
try:
# store user data to db
new_user = client.query(q.create(
q.collection('user'),
{'data': user}
))
except BadRequest:
flash('Email already exists')
else:
session['user_id'] = new_user['ref'].id()
flash('Account created successfully', 'success')
return redirect(url_for('dashboard'))
return render_template('signup.html')
A lot is going on here, the signin route takes form data from the signin.html file and processes it. First, I add a methods
argument to the route and add POST
AND GET
, it’s GET
by default. Then I checked if a user session is set, I will be explaining this in detail later. Then I also check if the request is sent via a POST request, if it is then I retrieve the user data and use Fauna’s client object to check if a user exists with that email, if no user exists I flash an error message to the user and redirect them back to the signin endpoint using url_for
and redirect
method in Flask. If the user exists I used the session object in Flask to log the user in. To store a session object all I need to do is
session[‘name’]=value
After storing the session I redirected them to the dashboard page. Also, the signup page is similar except for the fact that the data we retrieved from the browser is used in creating new data. I also confirmed that the email is unique, if it is not unique it will raise an error that I handled. Before dealing with the front-end codes it will be nice to work with all the server-side code, we will define two more endpoints and we will also update our home route.
Step 4: Defining dashboard and signout routes
@app.route('/dashboard/')
def dashboard():
if not session.get('user_id'):
flash('You need to be logged in to view this page!', 'warning')
return redirect(url_for('signin'))
user = client.query(
q.get(q.ref(q.collection("user"), session['user_id']))
)['data']
return render_template('dashboard.html', current_user=user)
@app.route("/signout/")
def signout():
if not session.get('user_id'):
flash('You need to be logged in to do this!', 'warning')
else:
session.pop('user_id', None)
flash('Signed out successfully', 'success')
return redirect(url_for('home'))
In the dashboard route, I first check if a user_id session is not set which means the request is unauthorized, if this condition is true I redirected back to the signin route. If the session is set, I used the user_id session variable to get the user data and I passed it to the HTML file that’s being rendered.
In the signup route, I first check if the user_id session isn’t set then I flash a message to the user but if the session is set I remove it and in either case, I redirect them back to the home route. I used the session.pop
function which takes the name we of the variable I want to remove from the session and I also pass in None
Edit the home route to be like the one below:
app.route('/')
def home():
if session.get('user_id'):
flash('You are logged in!', 'warning')
return redirect(url_for('dashboard'))
return render_template('home.html')
Before rendering the home.html file I first check if the user_id session is set, if it is then the user is signed in and I redirect back to the dashboard page.
Step 5: Creating the html files
In your signin.html
file type the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignIn - Fauna-Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="{{url_for('home')}}">Fauna-Login</a>
</div>
<ul class="nav navbar-nav">
<li><a href="{{url_for('signin')}}">SignIn</a></li>
<li><a href="{{url_for('signup')}}">Signup</a></li>
</ul>
</div>
</nav>
<!-- Page Content -->
<div class="container">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div class="col-md-4"></div>
<div class="container content-section col-md-4">
<h1 class="mt-4">Sign In</h1>
<form action="{{url_for('signin')}}" method="post">
<div class="form-group">
<label for="name" class="form-control-label">Email:</label>
<input class='form-control-lg form-control' type="email" name="email" id="" required>
</div>
<div>
<label width="100" for="name" class="form-control-label">Password:</label>
<input class='form-control-lg form-control' type="password" name="password" id="" required>
</div>
<div class="form-group">
<input type="submit" class="btn btn-outline-info" value="Sign In">
</div>
<div>Don't have an account? <a href="/signup">Signup</a></div>
</form>
</div>
</body>
</html>
In your signup.html
file type the following
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SignUp - Fauna-Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="{{url_for('home')}}">Fauna-Login</a>
</div>
<ul class="nav navbar-nav">
<li><a href="{{url_for('signin')}}">SignIn</a></li>
<li><a href="{{url_for('signup')}}" class="active">Signup</a></li>
</ul>
</div>
</nav>
<!-- Page Content -->
<div class="container">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div class="col-md-4"></div>
<div class="container content-section col-md-4">
<h1 class="mt-4">Sign In</h1>
<form action="{{url_for('signup')}}" method="post">
<div class="form-group">
<label for="name" class="form-control-label">Name:</label>
<input class='form-control-lg form-control' type="text" name="name" id="" required>
</div>
<div>
<label for="email" class="form-control-label">Email:</label>
<input class='form-control-lg form-control' type="email" name="email" id="" required>
</div>
<div>
<label for="name" class="form-control-label">Password:</label>
<input class='form-control-lg form-control' type="password" name="password" id="" required>
</div>
<div>
<label for="name" class="form-control-label">Confirm Password:</label>
<input class='form-control-lg form-control' type="password" name="confirm_password" id="" required>
</div>
<div class="form-group">
<input type="submit" class='btn btn-outline-info' value="Sign Up">
</div>
<div>Have an account? <a href="/signin">Login</a></div>
</form>
</div>
</body>
</html>
In the action of the form, I used url_for
which is a flask function that takes in a view function and returns the endpoint that the view function responds to. Alternatively, I can just pass the endpoint as I did at the end of the form.
In your dashboard.html
file type the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{current_user.name}} - Fauna-Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand active" href="#">Fauna-Login</a>
</div>
<ul class="nav navbar-nav">
<li><a href="{{url_for('signout')}}">Signout</a></li>
</ul>
</div>
</nav>
<!-- Page Content -->
<div>
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<h1>Welcome {{current_user.name}}</h1>
</body>
</html>
Testing The Application
Finally, we will be testing the application. First, go to the home route, you should see the following:
Click the signup button and try to sign up with an email and password whose length is not between 6 and 20 characters.
So we can see that our data is being validated before data creation. Next, create user data.
Click the signout button, you get a cool success message and you can signin again.
Click the signin button and try to sign in with the data you created while signing up.
If your data is correct you should be able to sign in successfully.
Database Check
Let’s view our user collection dashboard in Fauna
As you can see, the data is stored in the dashboard correctly. You can also query your data directly in your Fauna dashboard using FQL.
Conclusion
In this article, you have been able to build a web application that logs users in and also logs users out using two interesting technologies, Flask and Fauna. The source code for this project is available on GitHub If you find this article interesting please do share it with your friends and colleagues. You can reach out to me via Twitter if you have any questions.
Top comments (1)
With python login can be much faster as your app user base grows and people log-in and sign up all at the same time.