Skip to main content
  1. Posts/

Creating a RESTful API with APIFlask, JWT Token-based Security and PyTest

·680 words·4 mins·
netdevops blog apiflask jwt pytest
Maximilian Thoma
Author
Maximilian Thoma
network engineer
Table of Contents

In this blog post, I will show you how to create a simple RESTful API using APIFlask. Finally we will check with pytest if everything is working as expected. This API leverages JSON Web Tokens (JWT) for authentication and Bearer tokens for security. The entire code is written in Python and utilizes the Flask web framework.

Installing Required Libraries
#

Before we start, ensure you have the necessary libraries installed. Use the following pip commands:

pip install apiflask flask-jwt-extended pytest

Setting Up the API
#

Let’s start by importing the necessary modules and setting up the API:

from apiflask import APIFlask, Schema, HTTPTokenAuth
from apiflask.fields import Integer, String
from flask import jsonify, request
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity

Now, create an instance of APIFlask and configure the JWT Manager:

app = APIFlask(__name__)
app.config['JWT_SECRET_KEY'] = 'super-secret-key'  # Change this to a random secret key
jwt = JWTManager(app)
auth = HTTPTokenAuth(scheme='Bearer')

Defining Schemas
#

Next, let’s define the schemas for our data structures:

items = []

class UserSchema(Schema):
    username = String()
    password = String()

users = {
    'testuser': 'testpassword'
}

Creating Endpoints
#

Login Endpoint
#

The login endpoint allows a user to log in with a username and password and receive a JWT:

@app.post('/login')
@app.input(UserSchema)
def login(json_data):
    username = json_data.get('username')
    password = json_data.get('password')
    if username in users and users[username] == password:
        access_token = create_access_token(identity=username)
        return jsonify(access_token=access_token), 200
    return jsonify({"msg": "Bad username or password"}), 401

Verifying the Token
#

We use HTTPTokenAuth to verify the token:

@auth.verify_token
def verify_token(token):
    try:
        user_identity = get_jwt_identity()
        return user_identity
    except:
        return None

CRUD Endpoints for Items
#

Now, let’s define the endpoints for creating, retrieving, updating, and deleting items. All these endpoints are protected by JWT and token authentication:

@app.get('/items')
@jwt_required()
@app.auth_required(auth)
def get_items():
    return jsonify(items), 200

@app.post('/items')
@jwt_required()
@app.auth_required(auth)
def create_item():
    new_item = {'id': len(items) + 1, 'name': 'Item ' + str(len(items) + 1)}
    items.append(new_item)
    return jsonify(new_item), 201

@app.put('/items/<int:item_id>')
@jwt_required()
@app.auth_required(auth)
def update_item(item_id):
    for item in items:
        if item['id'] == item_id:
            item['name'] = 'Updated Item ' + str(item_id)
            return jsonify(item), 200
    return jsonify({'message': 'Item not found'}), 404

@app.delete('/items/<int:item_id>')
@jwt_required()
@app.auth_required(auth)
def delete_item(item_id):
    global items
    items = [item for item in items if item['id'] != item_id]
    return '', 204

Starting the Application
#

Finally, we start the application:

if __name__ == '__main__':
    app.run()

Adding Tests with Pytest
#

It’s important to ensure our API is well-tested. Here, we use pytest to test various aspects of our API.

Create a file test_app.py with the following content:

import pytest
from app2 import app

@pytest.fixture
def client():
    app.config['TESTING'] = True
    with app.test_client() as client:
        yield client

def test_login(client):
    response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
    assert response.status_code == 200
    assert 'access_token' in response.json

    response = client.post('/login', json={'username': 'testuser', 'password': 'wrongpassword'})
    assert response.status_code == 401

def test_get_items(client):
    login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
    token = login_response.json['access_token']
    response = client.get('/items', headers={'Authorization': f'Bearer {token}'})
    assert response.status_code == 200
    assert response.json == []

def test_create_item(client):
    login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
    token = login_response.json['access_token']
    response = client.post('/items', headers={'Authorization': f'Bearer {token}'})
    assert response.status_code == 201
    assert 'id' in response.json
    assert 'name' in response.json

def test_update_item(client):
    login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
    token = login_response.json['access_token']
    response = client.put('/items/1', headers={'Authorization': f'Bearer {token}'})
    assert response.status_code == 200
    assert response.json['name'] == 'Updated Item 1'

    response = client.put('/items/2', headers={'Authorization': f'Bearer {token}'})
    assert response.status_code == 404

def test_delete_item(client):
    login_response = client.post('/login', json={'username': 'testuser', 'password': 'testpassword'})
    token = login_response.json['access_token']
    response = client.delete('/items/1', headers={'Authorization': f'Bearer {token}'})
    assert response.status_code == 204

    response = client.get('/items', headers={'Authorization': f'Bearer {token}'})
    assert response.json == []

Running the Tests
#

Run the tests with the following command:

pytest

Results ….

====================================== test session starts =======================================
platform darwin -- Python 3.12.3, pytest-8.2.2, pluggy-1.5.0
rootdir: /Users/netdevops/_DEV/log
collected 5 items

test_app.py .....                                                                         [100%]

======================================= 5 passed in 0.13s ========================================

Summary
#

In this post, we created a simple RESTful API using APIFlask, leveraging JWT for authentication to secure our endpoints. We defined endpoints for logging in, creating, retrieving, updating, and deleting items, and tested our API with pytest. This example serves as a foundation that you can expand and tailor to your specific requirements.

Happy coding!

Related

APIFlask Webhook Listener for Netbox
·258 words·2 mins
netdevops blog netbox python api apiflask
This little code snippet is the base of my Netbox Webhook Listener written in APIFlask.
Validators / Converters / Formatter / Helper in Python
·743 words·4 mins
netdevops blog python validator helper converter
If you are engaged in NetDevOps, you need to validate and convert data, and occasionally, you’ll require some assistance.
Working with MongoDB & Python
·1357 words·7 mins
netdevops blog python mongodb
MongoDB is considered an easy-to-use database for several reasons, particularly when used in conjunction with PyMongo.