Create a Restful API in Flask with authentication required

- Andrés Cruz

En español
Create a Restful API in Flask with authentication required

A Rest API is a structure based on functions that are widely used today to communicate systems or different applications, each resource that we can create are simply functions that in the end are translated through URLs that are consumed via HTTP and its methods. :

  1. Get get
  2. Post Create
  3. Put or Patch Update
  4. Delete

Therefore, depending on the HTTP method we can give a different response, always respecting the purpose of each type of HTTP request.

Tools needed

So, to work with this package, the first thing we need is our Flask application and then install the package for Restful using:

pip install flask-restful

Now we are ready to use this package.

We are going to create a couple of classes for each entity that we want to make manageable, that is, to perform the CRUD on a model that remembers that a model is our connection to a table in the database.

Before starting

Remember that here we are not going to deal with how to work with SQLAlchemy to make the connections on our database with our project in Flask, we are simply going to make the CRUD type queries to operate the database.

Surely you are wondering why we need two classes and not just one, the answer is due to the definition of in the routes, therefore we will have:

  • A class for methods that do NOT need a pk or id to operate
  • A class for methods that DO require a pk or id to operate

Therefore the routes and configuration that we make are as follows:

from flask_restful import Api
from proyect_app.rest.controllers import BookApi, BookApiId
 
api = Api(app) #, decorators=[csrf.exempt]
api.add_resource(BookApi, '/api/book')
api.add_resource(BookApiId, '/api/book/<string:id>')

So let's create one to work without the IDs or PKs; as you can guess, this would be for Post methods, to create a record, and for get, to get ALL records.

We are going to work with the following model:

class Category(db.Model):
   __tablename__ = 'categories'
   id = db.Column(db.Integer, primary_key=True)
   name = db.Column(db.String(255))

Additional features on our package

Our package to achieve a Restful API brings several interesting elements that make life much easier for us to create this type of CRUD type APIs;

Get the request data

The first element would be a simple mechanism to get the request data easily through a dictionary:

Automatic object to dictionary conversion

Another very interesting element is that we have a decorator called marshal_with that allows us to indicate which fields we want to return from the model with which we are working each time we return the object; For that, we first have to define the structure that supports the aforementioned:

resource_fields = {
   'id': fields.Integer,
   'name' : fields.String
}

This decorator also optionally receives the json wrapper that it is going to return that we can configure using the envelope.

Other functionalities

We are going to create a helper class that will allow us to add some additional functionality on top of rest-like classes; for example, the protection with login.

And here define our methods

Método tipo get

   @marshal_with(resource_fields, envelope="categorias")
   def get(self):
       return Category.query.all()

With this function we are going to obtain all the records of our database:

Post type method

   @marshal_with(resource_fields, envelope="categoria")
   def post(self):
      
       parser = reqparse.RequestParser()
       parser.add_argument('name',required=True, help="No mandastes el nombre")
       args = parser.parse_args()
 
       c = Category(args['name'])
       db.session.add(c)
       db.session.commit()
 
       return c

Here we are going to work on creating a record, so we are going to create another class that WILL work with IDs:

Get type method

   @auth.login_required
   @marshal_with(resource_fields, envelope="categoria")
   def get(self, id):
       return self.abort_if_doesnt_exist(id,False)

Here we are going to return a record by its identifier:

Path or put method

   @marshal_with(resource_fields, envelope="categoria")
   def patch(self, id):
 
       parser = reqparse.RequestParser()
       parser.add_argument('name', required=True, help="No mandastes el nombre")
       args = parser.parse_args()
       c = self.abort_if_doesnt_exist(id, False)
       c.name = args['name']
       db.session.add(c)
       db.session.commit()
 
       return self.abort_if_doesnt_exist(c.id,False)

Delete type method

   def delete(self, id):
 
       c = self.abort_if_doesnt_exist(id, False)
 
       db.session.delete(c)
       db.session.commit()
 
       return {'msj':'ok'}

Finally, all the code of both classes:

 
from flask import request
from flask_restful import Resource, abort, reqparse, fields, marshal_with
from flask_httpauth import HTTPBasicAuth
 
from my_app.product.models import Category
from my_app.auth.model.user import User
from my_app import user_manager
from my_app import db
 
resource_fields = {
   'id': fields.Integer,
   'name' : fields.String
}
 
auth = HTTPBasicAuth()
 
@auth.verify_password
def verify_password(username, password):
   user = User.query.filter_by(username=username).first()
 
   if not user or not user_manager.verify_password(password_hash=user.password, password=password):
       return False
   return True
 
class Base:
   def category_to_json(self,category):
       return {
           'id': category.id,
           'name': category.name,
       }
 
   def abort_if_doesnt_exist(self, id, json=True):
       category = Category.query.get(id)
       if category is None:
           abort(404, message="Categoría {} no existe".format(id))
       if json:
           return self.category_to_json(category)
 
       return category
 
class CategoryRestFul(Resource,Base):
 
   @auth.login_required
   @marshal_with(resource_fields, envelope="categoria")
   def get(self, id):
       return self.abort_if_doesnt_exist(id,False)
 
   @marshal_with(resource_fields, envelope="categoria")
   def patch(self, id):
 
       parser = reqparse.RequestParser()
       parser.add_argument('name', required=True, help="No mandastes el nombre")       args = parser.parse_args()
 
       c = self.abort_if_doesnt_exist(id, False)
 
       c.name = args['name'] #request.form
 
       db.session.add(c)
       db.session.commit()
 
       return self.abort_if_doesnt_exist(c.id,False)
 
   def delete(self, id):
 
       c = self.abort_if_doesnt_exist(id, False)
 
       db.session.delete(c)
       db.session.commit()
 
       return {'msj':'ok'}
 
 
class CategoryRestFulList(Resource,Base):
 
   @marshal_with(resource_fields, envelope="categorias")
   def get(self):
       return Category.query.all()
 
   @marshal_with(resource_fields, envelope="categoria")
   def post(self):
      
       parser = reqparse.RequestParser()
       parser.add_argument('name',required=True, help="No mandastes el nombre")
       args = parser.parse_args()
 
       c = Category(args['name'])
       db.session.add(c)
       db.session.commit()
 
       return c
 
class CategoryRestFulListSearch(Resource,Base):
 
   @marshal_with(resource_fields, envelope="categoria")
   def get(self):
 
       parser = reqparse.RequestParser()
       parser.add_argument('search',required=True, help="Tienes que especificar la busqueda")
       args = parser.parse_args()
 
       return Category.query.filter(Category.name.like('%{0}%'.format(args['search']))).all()

Points to consider

As you can see, the operations we do in the Rest API have nothing unusual, some validations, operations on the database and little else, but as you can see, we have a very practical mechanism to manipulate or obtain the data of our user. ; the data that you pass us via a form, an axio request, or any API that you use to send data via HTTP, we obtain them through the RequestParser class; Ideally, this is that we can define validations in the efficiency of the add_argument function and therefore we can be 100 percent sure that the data we are working with is valid.

Authentication required in Restful with Flask

As you can see, we are using a function that ultimately works as a decorator on our Rest resources to do the required authentication on our Rest API; This is a third-party package that you can adapt to other types of services, but it works quite well with Flask Restful; We simply have to define a base function in which we perform the operations on our user model, whatever you want to verify in the required authentication phase of the Rest API, you can define it in that function.

In this example we have a simple example in which we first search for the username and its exists, then we verify the password that is in hash format in the database, therefore, it is a function that is in charge of making a manual login, and which we use perfectly in our Rest Api as required authentication.

@auth.verify_password
def verify_password(username, password):
   user = User.query.filter_by(username=username).first()
 
   if not user or not user_manager.verify_password(password_hash=user.password, password=password):
       return False
   return True

Remember that to use this authentication package you have to install it with:

pip install Flask-HTTPAuth
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.