This tutorial is out of date and no longer maintained.
In Part 1 of this series, we learned how to create a RESTful API the TDD way. We covered writing tests and learned a lot about Flask. If you haven’t read Part 1, please do because this tutorial will build upon it.
In this part of the series, we’ll learn how to authenticate and authorize users in our API.
In this tutorial, we’ll talk about securing our API with token-based authentication and user authorization. We will integrate users into the API we built in Part 1.
In order to get started, ensure your virtual environment is activated.
We intend to allow bucketlists to be owned by users. For now, anyone can manipulate a bucketlist even if they did not create it. We’ve got to fix this security hole.
How do we keep track of users, you ask? We define a model.
Here’s what we’ve done:
One-to-Many
relationship between the two tables. We defined this relationship by adding the db.relationship() function on the User table (parent table)cascade="all, delete-orphan"
will delete all bucketlists when a referenced user is deleted.generate_password_hash(password)
. This will make our users’ passwords be secure from dictionary and brute force attacks.get_all()
method to get all the bucketlists for a given user.Don’t forget to install Flask-Bcrypt
Migrate the changes we’ve just made to the database we initially created in Part 1 of the series.
Now we have a user table to keep track of registered users.
Our app will have many tests from now on. It’s best practice to have a test folder that will house all our tests. We’ll create a folder called tests
. Inside this folder, we’ll move our test_bucketlists.py
file into it.
Our directory structure should now look like this:
Also, we’ll edit the manage.py
as follows:
The decorator on top of test()
allows us to define a command called test
. Inside the function, we load the tests from the tests folder using the TestLoader()
class and then run them with TextTestRunner.run()
. If it’s successful, we exit gracefully with a return 0
.
Let’s test it out on our terminal.
The tests should fail. This is because we’ve not modified our code to work with the new changes in the model.
From now on, we’ll use this command to run our tests.
Token-based authentication is a security technique that authenticates users who attempt to login to a server using a security token provided by the server. Without the token, a user won’t be granted access to restricted resources. You can find more intricate details about token-based authentication here
For us to implement this authentication, we’ll use a Python package called PyJWT. PyJWT allows us to encode and decode JSON Web Tokens (JWT).
That being said, let’s install it:
For our users to authenticate, the access token is going to be placed in the Authorization HTTP header in all our bucketlist requests.
Here’s how the header looks like:
Authorization: "Bearer <The-access-token-is-here>"
We’ll put the word Bearer
before the token and separate them with a space character.
Don’t forget the space in between the Bearer
and the token.
We need to create a way to encode the token before it’s sent to the user. We also need to have a way to decode the token when the user sends it via the Authorization header.
In our model.py
we’ll create a function inside our User model
to generate the token and another one to decode it. Let’s add the following code:
The generate_token()
takes in a user ID as an argument, uses jwt
to create a token using the secret key, and makes it time-based by defining its expiration time. The token is valid for 5 minutes as specified in the timedelta. You can set it to your liking.
The decode_token()
takes in a token as an argument and checks whether the token is valid. If it is, it returns the user ID as the payload. It returns an error message if the token is expired or invalid.
Don’t forget to import jwt
and the datetime
above.
Our app is growing bigger. We’ll have to organize it into components. Flask uses the concept of Blueprints to make application components.
Blueprints are simply a set of operations that can be registered on a given app. Think of it as an extension of the app that can address a specific functionality.
We’ll create an authentication blueprint. This blueprint will focus on handling user registration and logins.
Inside our /app
directory create a folder and call it auth
.
Our auth
folder should contain:
__init__.py
fileviews.py
fileIn our auth/__init__.py
file, initialize a blueprint.
Then import the blueprint and register it at the bottom of the app/__init__.py
, just before the return app
line.
Testing should never be an afterthought. It should always come first.
We’re going to add a new test file that will house all our tests for the authentication blueprint. It’ll test whether our API can handle user registration, user log in, and access-token generation.
In our tests
directory, create a file naming it test_auth.py
. Write the following code in it:
We’ve initialized our test with a test client for making requests to our API and some test data.
The first test function test_registration()
sends a post request to /auth/register
and tests the response it gets. It ensures that the status code is 201, meaning we’ve successfully created a user.
The second test function tests whether the API can only register a user once. Having duplicates in the database is bad for business.
Now let’s run the tests using python manage.py test
. The tests should fail.
The reason our tests fail is simply because we lack the functionality they need to test. Let’s implement something that’ll make these two tests pass.
Open up the views.py
file and add the following code:
Here’s what we have added:
make_response
(for returning our response) and jsonify
(for encoding our data in JSON and adding an application/json
header to the response)POST
request to our post()
method.post()
method, we check if the user exists in our database. If they don’t, we create a new user and return a message to them notifying their successful registration.
If the user exists they are reminded to log in.as_view()
method to make our class-based view callable so that it can take a request and return a response. We then defined the URL for registering a user as /auth/register
.Let’s run our tests once more. Only the AuthTestCase tests should pass. The bucketlist tests still fail because we haven’t modified the __init__.py
code.
We’ll test our registration functionality by making a request using Postman.
But before we make the requests, ensure the API is up and running.
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 225-021-817
Now you can make a POST request to localhost:5000/auth/register
. Specify an email and a password of your choice to represent the user we are registering. Click send.
A user will have to log in to gain access to our API. Currently, we are lacking this login functionality. Let’s start with some tests. We’ll add two more tests at the bottom of our test_auth.py
as follows:
The test_user_login()
function tests whether our API can successfully log in as a registered user. It also tests for the access token.
The other test function test_non_registered_user_login()
tests whether our API can restrict signing in to only registered users.
Again, we’ll make the tests pass by implementing its functionality. Let’s create the login view.
Here, we’ve defined a class-based view just like we did in the registration section.
It dispatches the POST
request to the post()
method as well. This is to capture the user credentials (email, password) when they log in. It checks whether the password given is valid, generates an access token for the user, and returns a response containing the token.
We’ve also handled exceptions gracefully so that if one occurs, our API will continue running and won’t crush.
Finally, we defined a URL for the login route.
Make a POST request. Input the email and password we specified for the user during registration. Click send. You should get an access token in the JSON response.
If you run the tests, you will notice that the login tests pass, but the bucketlist one still fails. It’s time to refactor these tests.
First, we’ll create two helper functions for registering and signing in to our test user.
We do this so that when we want to register or log in as a test user (which is in all the tests), we don’t have to repeat ourselves. We’ll simply call the function and we are set.
Next, we’ll define a way to get the access token and add it to the Authorization header in all our client requests. Here’s a code snippet of how we’re going to do it.
We can now go ahead and refactor the whole test_bucketlist.py
file. After refactoring all our requests, we should have something like this:
We’ll refactor the methods that handle the HTTP requests for bucketlist creation and getting all the bucketlists. Open up /app/__init__.py
file and edit as follows:
We first added two imports: the User
model and the make_response
from Flask.
In the bucketlist function, we check for the authorization header from the request and extract the access token. Then, we decoded the token using User.decode_token(token)
to give us the payload. The payload is expected to be a user ID if the token is valid and not expired. If the token is not valid or expired, the payload will be an error message as a string.
Copy the token and paste it to the header section, creating an Authorization header. Don’t forget to put the word Bearer before the token with a space separating them like this:
Authorization: "Bearer dfg32r22349r40eiwoijr232394029wfopi23r2.2342..."
Make a POST request to localhost:5000/bucketlists/
, specifying the name of the bucketlist. Click send.
Ensure you’ve set the Authorization header just as we did for the POST request.
Make a GET request to localhost:5000/bucketlists/
and retrieve all the bucketlists our user just created.
We’ll refactor the PUT
and DELETE
functionality the same way we tackled the GET
and POST
.
Running python manage.py test
should now yield passing tests.
Now let’s test to see if it works on Postman.
Fire up the API using python run.py development
Make a GET request for a single bucketlist to localhost:5000/bucketlists/2
Feel free to play around with the PUT and DELETE functionality.
We’ve covered quite a lot on securing our API. We went through defining a user model and integrating users into our API. We also covered token-based authentication and used an authentication blueprint to implement it.
Even though our main focus is to write the code, we should not let testing be an afterthought. For us to improve on code quality, there have to be tests. Testing is the secret to increasing the agility of your product development. In everything project you do, put TTD first.
If you’ve coded this to the end, you are awesome!
Feel free to recommend this to friends and colleagues.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!