by

Flask API Quickstart Application with JSON Web Tokens, SQLAlchemy and Pytest

I created a sample Flask application that shows how to create HTTP API with authentication provided by JSON Web Tokens. It can be used to learn a bit about Flask, SQLAlchemy, JSON Web Tokens, Pytest and how it all works together. In particular, it shows how to:

  • hash passwords using bcrypt
  • issue and verify JWT with PyJWT
  • create Flask decorator to require token authentication on endpoints
  • use custom exceptions and an exception handler to return nice API responses on errors
  • use Flask-SQLAlchemy and Flask-migrate to describe data models, query data, create data and migrations
  • write simple tests in Pytest together with Flask test client
  • document the API using the newest OpenAPI Specification 3
  • use curl to query the API

The sample application is on Github in flask-api-quickstart repository. To start, clone it:

The whole application is best described by the API specification that can be found in docs/api.yaml. To get a nice visual sense of the API, put the contents of the file in Swagger editor.

Information about the requirements, installation, configuration and how to run the application are in the README file. In this blog post I want to cover implementation details and explain some choices I made.

App Structure

There are two main Python packages in the repository: app which is our Flask application and tests which stores our tests. When we run our application with python run.py, our app’s init module app/__init__.py is called. Here we can find our main initialization. The main Flask app object called app is created here and configured with parameters from app/config.py. We also create db object that represents SQLAlchemy to issue commands to the database. We can now import these two objects elsewhere in the application and use them. The last defined object migrationis not going to be used programmatically, but it is necessary for migration commands to work (e.g. flask db upgrade). In the rest of the init file we define application endpoints and error handlers. Note that here some of the imports are not placed at the top of the file to avoid circular imports.

Using Blueprint for auth endpoints

I separated authentication endpoints from the other ones using so called Blueprint. This Blueprint is defined in app/auth/views.pyas auth_apiand registered with the Flask application in the init file. This enables us to define the routes in a separate module and reuse a common URL prefix (/api/v1/auth). Blueprints are more powerful than what is used in the example, so be sure to check out the documentation to learn more about them.

Handling errors using custom exceptions and error handlers

In Flask it is typical to use  abort() function to indicate error. Abort function is configurable and so it could produce nice API error messages if we wanted to, but using custom exceptions instead is more flexible (e.g. exception can be suppressed on a higher layer of the application if desired). That’s why I decided to implement a few custom exception classes to represent errors. All of them derive from a base class called AppError:

As we can see, this class stores status and error codes and an additional error message. It can also produce JSON response object. All custom error exceptions are defined in  app/exceptions.py and app/auth/exceptions.py.

We also need to define exception handlers so that a correct output is sent when exception occurs. Here is an excerpt from the init file:

The first exception handler is to handle general “Not found” error, when someone is trying to access a route that is not defined. The second one will catch all exceptions except our custom exceptions and will produce general “Internal error” response. The last one will be called whenever we programmatically raise one of our exceptions, e.g by calling raise InvalidCredentialsError().

Data model

There are two domain classes: User that represents a registered user and BlacklistToken for storing revoked tokens. Models are defined in app/models.py using standard declarative approach available in SQLAlchemy.

Database calls are issued directly from the “service module” in app/auth/service.py, because I prefer not to put the code for db calls in the models themselves (Active Record pattern). For more complicated cases I’d create a separate module that would sit between “service” and “model” layers of the application.

We have already seen the configuration needed to enable database migrations. The first database migration is already generated, so when the migration is run using  flask db upgrade, we end up with the database schema we need. When starting from scratch, you can generate migrations using these set of commands:

JSON Web Tokens

Functions for working with JSON Web Tokens (encode_auth_token, decode_auth_token, etc.) can be found in app/auth/service.py.

In app/auth/helpers.py you will find an important part of the application: a decorator called auth_required. This decorator will ensure that a valid token is provided when accessing routes that use it, e.g.:

Testing

Writing tests using Pytest is pretty straightforward, but there is one thing that is needed to test Flask applications: Flask’s test client object that we use in the tests to set up data and call our endpoints:

This client parameter is Pytest’s fixture, defined in tests/conftest.py and automatically available in our test functions. This fixture is configured to set up our application with test configuration and set up the database tables before a test is executed, and clean up the database after it is run.

Final words

There are some advanced concepts in the example, but I tried to keep the application still very simple and straightforward. Some of the things in the application could be further divided into their own modules. For instance, the main endpoints could be placed in their own package instead of app/__init__.py. Functions in helpers.py and service.py could be also separated by domain into their own separate files. And for sure, there are some missing pieces (e.g. logging). Despite the flaws I hope that the project will be useful for some of you!

For the curious minds, the source code is formatted using black, a new Python formatter, but I removed it from the development dependencies for now, because it is still in beta.

If you have any question regarding the source code or suggestion for improvement, feel free to open a Github issue with label “question” or leave a comment here. In case you are interested in other Flask materials, check out my other Flask-based sample app called Rockets.

Happy coding!

Loading Likes...

Write a Comment

Comment