Build a RESTful API with Flask – The TDD Way: Part 2

In part 1 of this series, we learnt how to create a RESTful API the TDD way. We covered writing tests and learnt a lot about Flask.

In this part of the series, we'll learn how to authenticate and authorize users in our API. If you haven't read part 1, please do because this tutorial will build up on it. 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.

Table of Contents

    The User Model

    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.

    # app/models.py
    
    from app import db
    from flask_bcrypt import Bcrypt
    
    class User(db.Model):
        """This class defines the users table """
    
        __tablename__ = 'users'
    
        # Define the columns of the users table, starting with the primary key
        id = db.Column(db.Integer, primary_key=True)
        email = db.Column(db.String(256), nullable=False, unique=True)
        password = db.Column(db.String(256), nullable=False)
        bucketlists = db.relationship(
            'Bucketlist', order_by='Bucketlist.id', cascade="all, delete-orphan")
    
        def __init__(self, email, password):
            """Initialize the user with an email and a password."""
            self.email = email
            self.password = Bcrypt().generate_password_hash(password).decode()
    
        def password_is_valid(self, password):
            """
            Checks the password against it's hash to validates the user's password
            """
            return Bcrypt().check_password_hash(self.password, password)
    
        def save(self):
            """Save a user to the database.
            This includes creating a new user and editing one.
            """
            db.session.add(self)
            db.session.commit()
    
    class Bucketlist(db.Model):
        """This class defines the bucketlist table."""
    
        __tablename__ = 'bucketlists'
    
        # define the columns of the table, starting with its primary key
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(255))
        date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
        date_modified = db.Column(
            db.DateTime, default=db.func.current_timestamp(),
            onupdate=db.func.current_timestamp())
        created_by = db.Column(db.Integer, db.ForeignKey(User.id))
    
        def __init__(self, name, created_by):
            """Initialize the bucketlist with a name and its creator."""
            self.name = name
            self.created_by = created_by
    
        def save(self):
            """Save a bucketlist.
            This applies for both creating a new bucketlist
            and updating an existing onupdate
            """
            db.session.add(self)
            db.session.commit()
    
        @staticmethod
        def get_all(user_id):
            """This method gets all the bucketlists for a given user."""
            return Bucketlist.query.filter_by(created_by=user_id)
    
        def delete(self):
            """Deletes a given bucketlist."""
            db.session.delete(self)
            db.session.commit()
    
        def __repr__(self):
            """Return a representation of a bucketlist instance."""
            return "<Bucketlist: {}>".format(self.name)

    Here's what we've done:

    • We imported Flask-Bcrypt extension to help us in hashing our passwords. You should never store passwords in plaintext.
    • We created a User model that represents the users table. It contains the email and password fields to capture the user's credentials.
    • Since a user can own many bucketlists, we defined a One-to-Many relationship between the two tables. We defined this relationship by adding the db.relationship() function on the User table (parent table)
    • We added a foreign key on the child table (Bucketlist) referencing the User table. The foreign key has some arguments. The cascade="all, delete-orphan" will delete all bucketlists when a referenced user is deleted.
    • We hash the password by using generate_password_hash(pasword). This will make our users password be secure from dictionary and brute force attacks.
    • We refactored the get_all() method to get all the bucketlists for a given user.

    Don't forget to install Flask-Bcrypt

    (venv)$ pip install flask-bcrypt

    Migrate them

    Migrate the changes we've just made to the db we initially created in part 1 of the series.

    (venv)$    python manage.py db migrate
    (venv)$    python manage.py db upgrade

    Now we have a user table to keep track of registered users.

    Automate Tests

    Our app will have many tests from now on. It's best practice to have a test folder that will houses 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:

    ├── bucketlist
        ├── app
        │   ├── __init__.py
        │   └── models.py  
        ├── instance
        │   ├── __init__.py
        │   └── config.py
        ├── manage.py
        ├── requirements.txt
        ├── run.py
        ├── tests
        │   └── test_bucketlist.py

    Also, we'll edit the manage.py as follows:

    import os
    import unittest
    # class for handling a set of commands
    from flask_script import Manager
    from flask_migrate import Migrate, MigrateCommand
    from app import db, create_app
    
    # initialize the app with all its configurations
    app = create_app(config_name=os.getenv('APP_SETTINGS'))
    migrate = Migrate(app, db)
    # create an instance of class that will handle our commands
    manager = Manager(app)
    
    # Define the migration command to always be preceded by the word "db"
    # Example usage: python manage.py db init
    manager.add_command('db', MigrateCommand)
    
    # define our command for testing called "test"
    # Usage: python manage.py test
    @manager.command
    def test():
        """Runs the unit tests without test coverage."""
        tests = unittest.TestLoader().discover('./tests', pattern='test*.py')
        result = unittest.TextTestRunner(verbosity=2).run(tests)
        if result.wasSuccessful():
            return 0
        return 1
    
    if __name__ == '__main__':
        manager.run()

    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.

    (venv)$   python manage.py test

    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

    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:

    (venv)$  pip install PyJWT

    Securing Requests

    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.

    Encode and Decode 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:

    # /app/models.py
    
    ## previous  imports ###
    import jwt
    from datetime import datetime, timedelta
    
    class User(db.Model):
        """Maps to users table """
    
        __tablename__ = 'users'
    
        ###########################################
        ## Existing code for defining table columns is here  ##
        ###########################################
    
        def __init__(self, email, password):
            #### INIT CODE LIES HERE ###################
            ###########################################
    
        def password_is_valid(self, password):
            ##### PASSWORD CHECK CODE LIES HERE ####
            ###########################################
    
        def save(self):
            ######### CODE FOR SAVING USER LIES HERE ##
            ############################################
    
        def generate_token(self, user_id):
            """ Generates the access token"""
    
            try:
                # set up a payload with an expiration time
                payload = {
                    'exp': datetime.utcnow() + timedelta(minutes=5),
                    'iat': datetime.utcnow(),
                    'sub': user_id
                }
                # create the byte string token using the payload and the SECRET key
                jwt_string = jwt.encode(
                    payload,
                    current_app.config.get('SECRET'),
                    algorithm='HS256'
                )
                return jwt_string
    
            except Exception as e:
                # return an error in string format if an exception occurs
                return str(e)
    
        @staticmethod
        def decode_token(token):
            """Decodes the access token from the Authorization header."""
            try:
                # try to decode the token using our SECRET variable
                payload = jwt.decode(token, current_app.config.get('SECRET'))
                return payload['sub']
            except jwt.ExpiredSignatureError:
                # the token is expired, return an error string
                return "Expired token. Please login to get a new token"
            except jwt.InvalidTokenError:
                # the token is invalid, return an error string
                return "Invalid token. Please register or login"
    

    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 messsage if the token is expired or invalid.

    Don't forget to import jwt and the datetime above.

    The Auth Blueprint

    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 file
    • views.py file

    In our auth/__init__.py file, initialize a blueprint.

    # auth/__init__.py
    
    from flask import Blueprint
    
    # This instance of a Blueprint that represents the authentication blueprint
    auth_blueprint = Blueprint('auth', __name__)
    
    from . import views

    Then import the blueprint and register it at the bottom of the app/__init__.py, just before the return app line.

    # app/__init__.py
    
    # imports lie here
    
    def create_app(config_name):
        #####################################################
        ### Existing code for intializing the app with its configurations  ###
        #####################################################
    
        @app.route('/bucketlists/<int:id>', methods=['GET', 'PUT', 'DELETE'])
        def bucketlist_manipulation(id, **kwargs):
               #########################################################
            ### Existing code for creating, updating and deleting a bucketlist #####
            #########################################################
            ...
    
        # import the authentication blueprint and register it on the app
        from .auth import auth_blueprint
        app.register_blueprint(auth_blueprint)
    
        return app

    Test First, Implement Later

    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 login and access-token generation.

    In our tests directory, create a file naming it test_auth.py. Write the following code in it:

    # /tests/test_auth.py
    
    import unittest
    import json
    from app import create_app, db
    
    class AuthTestCase(unittest.TestCase):
        """Test case for the authentication blueprint."""
    
        def setUp(self):
            """Set up test variables."""
            self.app = create_app(config_name="testing")
            # initialize the test client
            self.client = self.app.test_client
            # This is the user test json data with a predefined email and password
            self.user_data = {
                'email': 'test@example.com',
                'password': 'test_password'
            }
    
            with self.app.app_context():
                # create all tables
                db.session.close()
                db.drop_all()
                db.create_all()
    
        def test_registration(self):
            """Test user registration works correcty."""
            res = self.client().post('/auth/register', data=self.user_data)
            # get the results returned in json format
            result = json.loads(res.data.decode())
            # assert that the request contains a success message and a 201 status code
            self.assertEqual(result['message'], "You registered successfully.")
            self.assertEqual(res.status_code, 201)
    
        def test_already_registered_user(self):
            """Test that a user cannot be registered twice."""
            res = self.client().post('/auth/register', data=self.user_data)
            self.assertEqual(res.status_code, 201)
            second_res = self.client().post('/auth/register', data=self.user_data)
            self.assertEqual(second_res.status_code, 202)
            # get the results returned in json format
            result = json.loads(second_res.data.decode())
            self.assertEqual(
                result['message'], "User already exists. Please login.")

    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.

    ----------------------------------------------------------------------
    raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

    User Registration View

    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 to pass.

    Open up the views.py file and add the following code:

    # /app/auth/views.py
    
    from . import auth_blueprint
    
    from flask.views import MethodView
    from flask import make_response, request, jsonify
    from app.models import User
    
    class RegistrationView(MethodView):
        """This class registers a new user."""
    
        def post(self):
            """Handle POST request for this view. Url ---> /auth/register"""
    
            # Query to see if the user already exists
            user = User.query.filter_by(email=request.data['email']).first()
    
            if not user:
                # There is no user so we'll try to register them
                try:
                    post_data = request.data
                    # Register the user
                    email = post_data['email']
                    password = post_data['password']
                    user = User(email=email, password=password)
                    user.save()
    
                    response = {
                        'message': 'You registered successfully. Please log in.'
                    }
                    # return a response notifying the user that they registered successfully
                    return make_response(jsonify(response)), 201
                except Exception as e:
                    # An error occured, therefore return a string message containing the error
                    response = {
                        'message': str(e)
                    }
                    return make_response(jsonify(response)), 401
            else:
                # There is an existing user. We don't want to register users twice
                # Return a message to the user telling them that they they already exist
                response = {
                    'message': 'User already exists. Please login.'
                }
    
                return make_response(jsonify(response)), 202
    
    registration_view = RegistrationView.as_view('register_view')
    # Define the rule for the registration url --->  /auth/register
    # Then add the rule to the blueprint
    auth_blueprint.add_url_rule(
        '/auth/register',
        view_func=registration_view,
        methods=['POST'])
    

    Here's what we have added:

    • We imported our blueprint together with Flask's make_response (for returning our response) and jsonify (for encoding our data in JSON and adding a application/json header to the response)
    • We've defined a class-based view to handle registration by dispatching a POST request to our post() method.
    • Inside our 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 login.
    • Lastly, we used 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.

    test_already_registered_user (test_auth.AuthTestCase)
    Test that a user cannot be registered twice. ... ok
    test_registration (test_auth.AuthTestCase)
    Test user registration works correcty. ... ok
    
    Bucketlist failed tests fall here
    ----------------------------------------------------------------------
    
    (venv)

    Using Postman [ /auth/register ]

    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.

    (venv) $ python run.py development
    
     * 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.

    User Login

    A user will have to login 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:

    # tests/test_auth.py
    class AuthTestCase(unittest.TestCase):
        """Test case for the authentication blueprint."""
    
        def setUp(self):
            #### EXISTING CODE FOR SETUP LIES HERE ####
    
        def test_registration(self):
            #### EXISTING TEST CODE LIES HERE ####
    
        def test_already_registered_user(self):
            ### EXISTING TEST CODE LIES HERE #####
    
        def test_user_login(self):
            """Test registered user can login."""
            res = self.client().post('/auth/register', data=self.user_data)
            self.assertEqual(res.status_code, 201)
            login_res = self.client().post('/auth/login', data=self.user_data)
    
            # get the results in json format
            result = json.loads(login_res.data.decode())
            # Test that the response contains success message
            self.assertEqual(result['message'], "You logged in successfully.")
            # Assert that the status code is equal to 200
            self.assertEqual(login_res.status_code, 200)
            self.assertTrue(result['access_token'])
    
        def test_non_registered_user_login(self):
            """Test non registered users cannot login."""
            # define a dictionary to represent an unregistered user
            not_a_user = {
                'email': 'not_a_user@example.com',
                'password': 'nope'
            }
            # send a POST request to /auth/login with the data above
            res = self.client().post('/auth/login', data=not_a_user)
            # get the result in json
            result = json.loads(res.data.decode())
    
            # assert that this response must contain an error message 
            # and an error status code 401(Unauthorized)
            self.assertEqual(res.status_code, 401)
            self.assertEqual(
                result['message'], "Invalid email or password, Please try again")
    

    The test_user_login() function tests whether our API can successfully login 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.

    Login View

    Again, we'll make the tests pass by implementing its functionality. Let's create the login view.

    from . import auth_blueprint
    
    from flask.views import MethodView
    from flask import Blueprint, make_response, request, jsonify
    from app.models import User
    
    class RegistrationView(MethodView):
        """This class-based view registers a new user."""
        #### EXISTING REGISTRATION CODE HERE ####
        ##########################################
    
    class LoginView(MethodView):
        """This class-based view handles user login and access token generation."""
    
        def post(self):
            """Handle POST request for this view. Url ---> /auth/login"""
            try:
                # Get the user object using their email (unique to every user)
                user = User.query.filter_by(email=request.data['email']).first()
    
                # Try to authenticate the found user using their password
                if user and user.password_is_valid(request.data['password']):
                    # Generate the access token. This will be used as the authorization header
                    access_token = user.generate_token(user.id)
                    if access_token:
                        response = {
                            'message': 'You logged in successfully.',
                            'access_token': access_token.decode()
                        }
                        return make_response(jsonify(response)), 200
                else:
                    # User does not exist. Therefore, we return an error message
                    response = {
                        'message': 'Invalid email or password, Please try again'
                    }
                    return make_response(jsonify(response)), 401
    
            except Exception as e:
                # Create a response containing an string error message
                response = {
                    'message': str(e)
                }
                # Return a server error using the HTTP Error Code 500 (Internal Server Error)
                return make_response(jsonify(response)), 500
    
    # Define the API resource
    registration_view = RegistrationView.as_view('registration_view')
    login_view = LoginView.as_view('login_view')
    
    # Define the rule for the registration url --->  /auth/register
    # Then add the rule to the blueprint
    auth_blueprint.add_url_rule(
        '/auth/register',
        view_func=registration_view,
        methods=['POST'])
    
    # Define the rule for the registration url --->  /auth/login
    # Then add the rule to the blueprint
    auth_blueprint.add_url_rule(
        '/auth/login',
        view_func=login_view,
        methods=['POST']
    )

    Here, we've defined a class-based view just like we did on 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 login. 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.

    Logging in on Postman [ /auth/login ]

    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.

    Running the tests

    If you run the tests, you will notice that the login tests pass, but the bucketlist one still fail. It's time to refactor this tests.

    Refactor Bucketlist Tests

    First, we'll create two helper functions for registering and signing in our test user.

    # tests/test_bucketlist.py
    class BucketlistTestCase(unittest.TestCase):
        """This class represents the bucketlist test case"""
    
        def setUp(self):
            """Set up test variables."""
         #### SETUP VARIABLES ARE HERE #####
         ####################################
    
        def register_user(self, email="user@test.com", password="test1234"):
            """This helper method helps register a test user."""
            user_data = {
                'email': email,
                'password': password
            }
            return self.client().post('/auth/register', data=user_data)
    
        def login_user(self, email="user@test.com", password="test1234"):
            """This helper method helps log in a test user."""
            user_data = {
                'email': email,
                'password': password
            }
            return self.client().post('/auth/login', data=user_data)
    
        ############################################
        ##### ALL OUR TESTS METHODS LIE HERE #######
    
    # Make the tests conveniently executable
    if __name__ == "__main__":
        unittest.main()

    We do this so that when we want to register or login 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 the how were going to do it.

        def test_bucketlist_creation(self):
            """Test the API can create a bucketlist (POST request)"""
            # register a test user, then log them in
            self.register_user():
            result = self.login_user()
            # obtain the access token
            access_token = json.loads(result.data.decode())['access_token']
    
            # ensure the request has an authorization header set with the access token in it
            res = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data=self.bucketlist)

    We can now go ahead and refactor the whole test_bucketlist.py file. After refactoring all our request, we should have something like this:

    import unittest
    import os
    import json
    from app import create_app, db
    
    class BucketlistTestCase(unittest.TestCase):
        """This class represents the bucketlist test case"""
    
        def setUp(self):
            """Define test variables and initialize app."""
            self.app = create_app(config_name="testing")
            self.client = self.app.test_client
            self.bucketlist = {'name': 'Go to Borabora for vacay'}
    
            # binds the app to the current context
            with self.app.app_context():
                # create all tables
                db.session.close()
                db.drop_all()
                db.create_all()
    
        def register_user(self, email="user@test.com", password="test1234"):
            user_data = {
                'email': email,
                'password': password
            }
            return self.client().post('/auth/register', data=user_data)
    
        def login_user(self, email="user@test.com", password="test1234"):
            user_data = {
                'email': email,
                'password': password
            }
            return self.client().post('/auth/login', data=user_data)
    
        def test_bucketlist_creation(self):
            """Test API can create a bucketlist (POST request)"""
            self.register_user()
            result = self.login_user()
            access_token = json.loads(result.data.decode())['access_token']
    
            # create a bucketlist by making a POST request
            res = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data=self.bucketlist)
            self.assertEqual(res.status_code, 201)
            self.assertIn('Go to Borabora', str(res.data))
    
        def test_api_can_get_all_bucketlists(self):
            """Test API can get a bucketlist (GET request)."""
            self.register_user()
            result = self.login_user()
            access_token = json.loads(result.data.decode())['access_token']
    
            # create a bucketlist by making a POST request
            res = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data=self.bucketlist)
            self.assertEqual(res.status_code, 201)
    
            # get all the bucketlists that belong to the test user by making a GET request
            res = self.client().get(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
            )
            self.assertEqual(res.status_code, 200)
            self.assertIn('Go to Borabora', str(res.data))
    
        def test_api_can_get_bucketlist_by_id(self):
            """Test API can get a single bucketlist by using it's id."""
            self.register_user()
            result = self.login_user()
            access_token = json.loads(result.data.decode())['access_token']
    
            rv = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data=self.bucketlist)
    
            # assert that the bucketlist is created 
            self.assertEqual(rv.status_code, 201)
            # get the response data in json format
            results = json.loads(rv.data.decode())
    
            result = self.client().get(
                '/bucketlists/{}'.format(results['id']),
                headers=dict(Authorization="Bearer " + access_token))
            # assert that the bucketlist is actually returned given its ID
            self.assertEqual(result.status_code, 200)
            self.assertIn('Go to Borabora', str(result.data))
    
        def test_bucketlist_can_be_edited(self):
            """Test API can edit an existing bucketlist. (PUT request)"""
            self.register_user()
            result = self.login_user()
            access_token = json.loads(result.data.decode())['access_token']
    
            # first, we create a bucketlist by making a POST request
            rv = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data={'name': 'Eat, pray and love'})
            self.assertEqual(rv.status_code, 201)
            # get the json with the bucketlist
            results = json.loads(rv.data.decode())
    
            # then, we edit the created bucketlist by making a PUT request
            rv = self.client().put(
                '/bucketlists/{}'.format(results['id']),
                headers=dict(Authorization="Bearer " + access_token),
                data={
                    "name": "Dont just eat, but also pray and love :-)"
                })
            self.assertEqual(rv.status_code, 200)
    
            # finally, we get the edited bucketlist to see if it is actually edited.
            results = self.client().get(
                '/bucketlists/{}'.format(results['id']),
                headers=dict(Authorization="Bearer " + access_token))
            self.assertIn('Dont just eat', str(results.data))
    
        def test_bucketlist_deletion(self):
            """Test API can delete an existing bucketlist. (DELETE request)."""
            self.register_user()
            result = self.login_user()
            access_token = json.loads(result.data.decode())['access_token']
    
            rv = self.client().post(
                '/bucketlists/',
                headers=dict(Authorization="Bearer " + access_token),
                data={'name': 'Eat, pray and love'})
            self.assertEqual(rv.status_code, 201)
            # get the bucketlist in json
            results = json.loads(rv.data.decode())
    
            # delete the bucketlist we just created
            res = self.client().delete(
                '/bucketlists/{}'.format(results['id']),
                headers=dict(Authorization="Bearer " + access_token),)
            self.assertEqual(res.status_code, 200)
    
            # Test to see if it exists, should return a 404
            result = self.client().get(
                '/bucketlists/1',
                headers=dict(Authorization="Bearer " + access_token))
            self.assertEqual(result.status_code, 404)
    
    # Make the tests conveniently executable
    if __name__ == "__main__":
        unittest.main()

    Refactor GET(all) and POST functionality

    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:

    # /app/__init__.py
    
    ## imports ##
    from flask import request, jsonify, abort, make_response
    
    def create_app(config_name):
        from models import Bucketlist, User
    
        ###########################################
        ### EXISTING APP CONFIG CODE LIES HERE ###
        ###########################################
    
        @app.route('/bucketlists/', methods=['POST', 'GET'])
        def bucketlists():
            # Get the access token from the header
            auth_header = request.headers.get('Authorization')
            access_token = auth_header.split(" ")[1]
    
            if access_token:
             # Attempt to decode the token and get the User ID
                user_id = User.decode_token(access_token)
                if not isinstance(user_id, str):
                    # Go ahead and handle the request, the user is authenticated
    
                    if request.method == "POST":
                        name = str(request.data.get('name', ''))
                        if name:
                            bucketlist = Bucketlist(name=name, created_by=user_id)
                            bucketlist.save()
                            response = jsonify({
                                'id': bucketlist.id,
                                'name': bucketlist.name,
                                'date_created': bucketlist.date_created,
                                'date_modified': bucketlist.date_modified,
                                'created_by': user_id
                            })
    
                            return make_response(response), 201
    
                    else:
                        # GET all the bucketlists created by this user
                        bucketlists = Bucketlist.query.filter_by(created_by=user_id)
                        results = []
    
                        for bucketlist in bucketlists:
                            obj = {
                                'id': bucketlist.id,
                                'name': bucketlist.name,
                                'date_created': bucketlist.date_created,
                                'date_modified': bucketlist.date_modified,
                                'created_by': bucketlist.created_by
                            }
                            results.append(obj)
    
                        return make_response(jsonify(results)), 200
                else:
                    # user is not legit, so the payload is an error message
                    message = user_id
                    response = {
                        'message': message
                    }
                    return make_response(jsonify(response)), 401
    

    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.

    Test it on Postman

    Create a bucketlist or two

    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.

    Get all bucketlists for a given user

    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.

    Finally, Refactor GET(one), PUT and DELETE functionality

    We'll refactor the PUT and DELETE functionality the same way we tackled the GET and POST.

    
    # /app/__init__.py
    
    ## imports ##
    from flask import request, jsonify, abort, make_response
    
    def create_app(config_name):
        from models import Bucketlist, User
    
        ############################################################
        ### Existing code for initializing the app with its configurations lies here ###
        ############################################################
    
        @app.route('/bucketlists/', methods=['POST', 'GET'])
        def bucketlists():
            #### CODE FOR  GET and POST LIES HERE#####
            ###############################
    
        @app.route('/bucketlists/<int:id>', methods=['GET', 'PUT', 'DELETE'])
        def bucketlist_manipulation(id, **kwargs):
            # get the access token from the authorization header
            auth_header = request.headers.get('Authorization')
            access_token = auth_header.split(" ")[1]
    
            if access_token:
                # Get the user id related to this access token
                user_id = User.decode_token(access_token)
    
                if not isinstance(user_id, str):
                    # If the id is not a string(error), we have a user id
                    # Get the bucketlist with the id specified from the URL (<int:id>)
                    bucketlist = Bucketlist.query.filter_by(id=id).first()
                    if not bucketlist:
                        # There is no bucketlist with this ID for this User, so
                        # Raise an HTTPException with a 404 not found status code
                        abort(404)
    
                    if request.method == "DELETE":
                        # delete the bucketlist using our delete method
                        bucketlist.delete()
                        return {
                            "message": "bucketlist {} deleted".format(bucketlist.id)
                        }, 200
    
                    elif request.method == 'PUT':
                        # Obtain the new name of the bucketlist from the request data
                        name = str(request.data.get('name', ''))
    
                        bucketlist.name = name
                        bucketlist.save()
    
                        response = {
                            'id': bucketlist.id,
                            'name': bucketlist.name,
                            'date_created': bucketlist.date_created,
                            'date_modified': bucketlist.date_modified,
                            'created_by': bucketlist.created_by
                        }
                        return make_response(jsonify(response)), 200
                    else:
                        # Handle GET request, sending back the bucketlist to the user
                        response = {
                            'id': bucketlist.id,
                            'name': bucketlist.name,
                            'date_created': bucketlist.date_created,
                            'date_modified': bucketlist.date_modified,
                            'created_by': bucketlist.created_by
                        }
                        return make_response(jsonify(response)), 200
                else:
                    # user is not legit, so the payload is an error message
                    message = user_id
                    response = {
                        'message': message
                    }
                    # return an error response, telling the user he is Unauthorized
                    return make_response(jsonify(response)), 401
    
        # import the authentication blueprint and register it on the app
        from .auth import auth_blueprint
        app.register_blueprint(auth_blueprint)
    
        return app
    

    Running python manage.py test should now yield passing tests.

    test_already_registered_user (test_auth.AuthTestCase)
    Test that a user cannot be registered twice. ... ok
    test_non_registered_user_login (test_auth.AuthTestCase)
    Test non registered users cannot login. ... ok
    test_registration (test_auth.AuthTestCase)
    Test user registration works correcty. ... ok
    test_user_login (test_auth.AuthTestCase)
    Test registered user can login. ... ok
    test_api_can_get_all_bucketlists (test_bucketlist.BucketlistTestCase)
    Test API can get a bucketlist (GET request). ... ok
    test_api_can_get_bucketlist_by_id (test_bucketlist.BucketlistTestCase)
    Test API can get a single bucketlist by using it's id. ... ok
    test_bucketlist_can_be_edited (test_bucketlist.BucketlistTestCase)
    Test API can edit an existing bucketlist. (PUT request) ... ok
    test_bucketlist_creation (test_bucketlist.BucketlistTestCase)
    Test API can create a bucketlist (POST request) ... ok
    test_bucketlist_deletion (test_bucketlist.BucketlistTestCase)
    Test API can delete an existing bucketlist. (DELETE request). ... ok
    
    ----------------------------------------------------------------------
    Ran 9 tests in 1.579s
    
    OK
    (venv)

    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.

    Conclusion

    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 has 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.

    Jee Gikera

    5 posts

    A full-stack software engineer, Jee believes in doing it today, not tomorrow. He enjoys gaming, lively hangouts with friends and family, and when away – the quest for rare coins.