Let’s learn how to secure a REST API with JSON web tokens to prevent users and third-party applications from abusing it.
We will build a database service using SQLite and allow users to access it via a REST API using HTTP methods such as POST and PUT.
In addition, we will get to know why JSON web tokens is a suitable way to protect rest API instead of digest and basic authentication. Before we proceed, let’s understand the term JSON web tokens, REST API and Flask framework.
JSON Web Tokens
JSON web token, also known as JWT, is the secure way of transferring random tokens between two parties or entities. JSON is usually made up of three parts as the following.
JSON uses two types of structure forms when transferring data or information between two parties.
The serialized form is used when transferring data to the network through each request and response whilst the deserialized form is used when reading and writing data to the web token.
In the serialized form, there are three components.
The header component defines the cryptographic information about the tokens. For example:
- Is it signed or unsigned JWT?
- Define algorithm techniques
The deserialized form, unlike the serialized form, contains two components.
API (application programming interface) allows communication between two applications to retrieve or submit the data. There are two popular types of APIs – web and system API.
In this article, we will only look at the web API. There are two types of web API.
- Request – Response API: Rest, GraphQL, Remote Procedure Call (RPC)
- Event-Driven API: WebHooks, Web Sockets, HTTP Streaming
REST API falls under the request-response category. It makes use of HTTP methods such as GET, POST, and PUT to perform API operations.
A classic example is when a user sends a GET method to the web service to request for or retrieve a specific resource or a collection of resources. The server then sends back the specific resource or collection of resources back to the user who requested it.
Flask is a framework based on python. It is a micro-framework used by python developers to build rest API. It is called a micro framework because it allows developers, for instance, to add custom authentication and any other backend system based on preferences.
Let’s get it started with the implementation. My system setup is as follows.
- Ubuntu as OS
- Python 2.7+
Set up a virtual environment using virtualenv
We need to set up a virtual environment to ensure that some packages will not conflict with system packages. Let’s use the
virtualenv to set up a new virtual environment.
Assuming you have the
pip command available on your system, run the following command via
pip to install.
pip install virtualenv
If you don’t have pip on your machine, then follow this documentation to install pip on your system.
Next, let’s create a directory to store or hold our virtual environment. Use the
mkdir command shown below to create a directory
Change into the
flaskproject directory using the following command
flaskproject directory, use the
virtualenv tool to create a virtual environment as shown below:
After you have used the
virtualenv tool to create the virtual environment, run the
cd command to change into the
flaskapi directory as the virtual environment and activate it using the command below.
Execute all tasks related to this project within the virtual environment.
Install packages using pip
Now it’s time to install packages such as the flask framework and PyJWT which we will use to build the rest API and other necessary packages for our API project.
requirements.txt file with the following packages.
Flask datetime uuid Flask-SQLAlchemy PyJWT
Install them with pip.
pip install -r requirements.txt
Set up a database
Let’s install SQLite.
apt-get install sqlite3
Create a database named the library. Inside this database, we will create two tables, namely the
Users table will contain registered users. Only registered users can have access to the Authors table.
Authors table will store authors’ information or details such as the name of the author, country of birth and so on submitted by the registered users.
Create the database using the following command:
You can check whether you have successfully created the database by using the command below:
Open a new terminal and execute the following in the virtual environment we created earlier.
Paste the following code inside the file named
from flask import Flask, request, jsonify, make_response from flask_sqlalchemy import SQLAlchemy from werkzeug.security import generate_password_hash, check_password_hash import uuid import jwt import datetime from functools import wraps
The first line in the code above imports packages such as
jsonify. We will make use of
request to keep track of the request-level data during a request and use
jsonify to output responses in a JSON format.
On the next line, we imported SQLAlchemy from
flask_sqlalchemy in order to integrate SQLAlchemy features into the flask.
werkzeug.security, we imported
generate_password_hash to generate password hash for users and
check_password_hash to check the user’s password when comparing password submitted by users with users’ passwords stored in the database.
Finally, we imported
uuid also known as universal unique identifiers to generate random id numbers for users.
Still, inside the
app.py file, implement the configuration settings for the library API using the code below inside the app.py file.
Place the following code beneath the import statement.
app = Flask(__name__) app.config['SECRET_KEY']='Th1s1ss3cr3t' app.config['SQLALCHEMY_DATABASE_URI']='sqlite://///home/michael/geekdemos/geekapp/library.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True db = SQLAlchemy(app)
Now create two models for the Users and Authors table as shown below. Copy and paste the code inside the app.py file.
Place the code below right beneath this database setting
db = SQLAlchemy(app)
class Users(db.Model): id = db.Column(db.Integer, primary_key=True) public_id = db.Column(db.Integer) name = db.Column(db.String(50)) password = db.Column(db.String(50)) admin = db.Column(db.Boolean)
class Authors(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False)) book = db.Column(db.String(20), unique=True, nullable=False)) country = db.Column(db.String(50), nullable=False)) booker_prize = db.Column(db.Boolean)
Generate Users and Authors Tables
On the terminal, type the following code inside the virtual environment to generate or create tables for both the Users and Authors tables as shown below
from app import db db.create_all()
Afterward, open the
app.py file inside the virtual environment and create another function.
This function will generate tokens in order to allow only registered users to access and perform a set of API operations against the Authors table.
Place this code beneath the database model for the Authors table
def token_required(f): @wraps(f) def decorator(*args, **kwargs): token = None if 'x-access-tokens' in request.headers: token = request.headers['x-access-tokens'] if not token: return jsonify() try: data = jwt.decode(token, app.config[SECRET_KEY]) current_user = Users.query.filter_by(public_id=data['public_id']).first() except: return jsonify() return f(current_user, *args, **kwargs) return decorator
Create routes for the users table
Now let’s create a route to allow users to register for the Authors API via a username and password as shown below.
Again open the
app.py file inside the virtual environment and paste the following code beneath the function
@app.route('/register', methods=['GET', 'POST']) def signup_user(): data = request.get_json() hashed_password = generate_password_hash(data['password'], method='sha256') new_user = Users(public_id=str(uuid.uuid4()), name=data['name'], password=hashed_password, admin=False) db.session.add(new_user) db.session.commit() return jsonify()
Inside the virtual environment, create another route in the
app.py file to allow registered users to login.
When a user logs in, a random token is generated for the user to access the library API.
Paste the code below beneath the previous route we created.
@app.route('/login', methods=['GET', 'POST']) def login_user(): auth = request.authorization if not auth or not auth.username or not auth.password: return make_response('could not verify', 401, ) user = Users.query.filter_by(name=auth.username).first() if check_password_hash(user.password, auth.password): token = jwt.encode(, app.config['SECRET_KEY']) return jsonify() return make_response('could not verify', 401, )
Still, within the virtual environment, create another route in the
app.py file to get or retrieve all registered users.
This code checks for all registered users in the Users table and returns the final result in a JSON format.
Paste the code below beneath the login route
@app.route('/users', methods=['GET']) def get_all_users(): users = Users.query.all() result =  for user in users: user_data = user_data['public_id'] = user.public_id user_data['name'] = user.name user_data['password'] = user.password user_data['admin'] = user.admin result.append(user_data) return jsonify()
Let’s create routes for the Authors table to allow users to retrieve all authors in the database, as well as delete authors.
Only users with valid tokens can perform these API operations.
Inside the app.py file, create a route for registered users to create new authors.
Paste this code beneath the route which allows a user to retrieve all registered users.
@app.route('/author', methods=['POST', 'GET']) @token_required def create_author(current_user): data = request.get_json() new_authors = Authors(name=data['name'], country=data['country'], book=data['book'], booker_prize=True, user_id=current_user.id) db.session.add(new_authors) db.session.commit() return jsonify()
Next, create another route to allow a registered user with a valid token to retrieve all authors in the Authors table as shown below.
Paste this code below the route which allows a user to create a new author.
@app.route('/authors', methods=['POST', 'GET']) @token_required def get_authors(current_user): authors = Authors.query.filter_by(user_id=current_user.id).all() output =  for author in authors: author_data = author_data['name'] = author.name author_data['book'] = author.book author_data['country'] = author.country author_data['booker_prize'] = author.booker_prize output.append(author_data) return jsonify()
Finally, still inside the
app.py file, create a route to delete a specified author as shown below.
Paste this code beneath the route which allows a user to retrieve a list of authors.
@app.route('/authors/<author_id>', methods=['DELETE']) @token_required def delete_author(current_user, author_id): author = Author.query.filter_by(id=author_id, user_id=current_user.id).first() if not author: return jsonify() db.session.delete(author) db.session.commit() return jsonify() if __name__ == '__main__': app.run(debug=True)
Afterward, save and close the app.py file inside the virtual environment.
Testing the library API with Postman
In this section, we will make use of a postman tool to send a request to the database services. If you don’t have a postman on your machine, you can find out how to download and install it here.
Apart from the postman, we can make use of other tools such as Curl to send requests to the server.
Open a new terminal and type the following:
postman will cause your web browser to display the page below:
You can decide to sign up and create a free account but we will skip and get direct access to the app to test the library API as shown below:
In this section, we will allow a user to register for the library API by providing a username and a unique password in a JSON format using the POST method using the steps below:
- Click on the tab labeled Body
- Then select the raw button and choose the JSON format
- enter a username and password to register as shown in the screenshot
- Beside the send button, insert the following URL http://127.0.0.1/register
- Finally, change the method to POST and press the send button.
It will display the following output as shown below:
Now we have successfully registered a user. Let’s go-ahead to allow the user who just registered to login in order to generate a temporary random token to access the Authors table using the following steps:
- Click on the authorization tab.
- Under the type section, select basic authentication.
- Then fill the username and password form with the username and password you registered with previously.
- Finally, press the send button to login and generate a random token.
Once the user login successfully, a random token is generated for the user as shown in the screenshot.
We will make use of the generated random token to access the Authors table.
In this section, we will add an author’s information to the Authors table via the POST method using the following steps:
- Click on the headers tab
- Include the following HTTP headers shown in the screenshot
- Next, click on the body tab and enter the details of the new author
- Then press the send button to add the author’s details to the Author’s table
You can also retrieve authors’ information in the Authors table via the following:
- Make sure your generated token is in the headers section. if it is not there, you need to fill it with your token.
- Beside the send button, enter this URL
- Then change the HTTP method to GET and press the send button to retrieve the authors details.
Finally, you can delete the author(s) in the Authors table via the
DELETE method using the following steps:
- Make sure your token is still in the headers section. You can check the headers tab to ensure the necessary information is in place.
- Beside the send button, enter this URL
- Then press the send button to delete the user you specified.
You can find the complete source code on Github. You can clone it and check it out on your machine.