¬ŅC√≥mo proteger una API REST de Flask con JSON Web Token?

Aprendamos cómo proteger una API REST con tokens web JSON para evitar que los usuarios y las aplicaciones de terceros abusen de ella.

Construiremos un servicio de base de datos usando SQLite y permitiremos que los usuarios accedan a él a través de una API REST usando métodos HTTP como POST y PUT.

Además, conoceremos por qué los tokens web JSON son una forma adecuada de proteger la API de descanso en lugar de la autenticación básica y de resumen. Antes de continuar, entendamos el término tokens web JSON, API REST y marco Flask.

Tokens web JSON

El token web JSON, también conocido como JWT, es la forma segura de transferir tokens aleatorios entre dos partes o entidades. JSON generalmente se compone de tres partes como las siguientes.

JSON utiliza dos tipos de formularios de estructura al transferir datos o información entre dos partes.

El formulario serializado se usa cuando se transfieren datos a la red a través de cada solicitud y respuesta, mientras que el formulario deserializado se usa cuando se leen y escriben datos en el token web.

En la forma serializada, hay tres componentes.

El componente de encabezado define la información criptográfica sobre los tokens. Por ejemplo:

¬ŅEs JWT firmado o sin firmar? Definir t√©cnicas de algoritmo

El formulario deserializado, a diferencia del formulario serializado, contiene dos componentes.

API REST

La API (interfaz de programación de aplicaciones) permite la comunicación entre dos aplicaciones para recuperar o enviar los datos. Hay dos tipos populares de API: web y API del sistema.

En este artículo, solo veremos la API web. Hay dos tipos de API web.

Solicitud ‚Äď Respuesta API: Rest, GraphQL, Llamada a procedimiento remoto (RPC) API impulsada por eventos: WebHooks, Web Sockets, HTTP Streaming

La API REST cae dentro de la categoría de solicitud-respuesta. Hace uso de métodos HTTP como GET, POST y PUT para realizar operaciones API.

Un ejemplo clásico es cuando un usuario envía un método GET al servicio web para solicitar o recuperar un recurso específico o una colección de recursos. Luego, el servidor devuelve el recurso específico o la colección de recursos al usuario que lo solicitó.

Marco de matraz

Flask es un marco basado en Python. Es un micromarco utilizado por los desarrolladores de python para crear una API de descanso. Se llama micro framework porque permite a los desarrolladores, por ejemplo, agregar autenticación personalizada y cualquier otro sistema de back-end basado en preferencias.

Comencemos con la implementación. La configuración de mi sistema es la siguiente.

Ubuntu como OS Python 2.7+ Cartero

Configurar un entorno virtual usando virtualenv

Necesitamos configurar un entorno virtual para garantizar que algunos paquetes no entren en conflicto con los paquetes del sistema. Usemos virtualenv para configurar un nuevo entorno virtual.

Suponiendo que tiene el comando pip disponible en su sistema, ejecute el siguiente comando a través de pip para instalarlo.

pip install virtualenv

Si no tiene pip en su máquina, siga esta documentación para instalar pip en su sistema.

A continuación, creemos un directorio para almacenar o mantener nuestro entorno virtual. Use el comando mkdir que se muestra a continuación para crear un directorio

mkdir flaskproject

Cambie al directorio del proyecto de matraz usando el siguiente comando

cd flaskproject

Dentro del directorio del proyecto de la botella, use la herramienta virtualenv para crear un entorno virtual como se muestra a continuación:

virtualenv flaskapi

Una vez que haya utilizado la herramienta virtualenv para crear el entorno virtual, ejecute el comando cd para cambiar al directorio de Flaspapi como entorno virtual y actívelo con el siguiente comando.

source bin/activate

Ejecutar todas las tareas relacionadas con este proyecto dentro del entorno virtual.

Instalar paquetes usando pip

Ahora es el momento de instalar paquetes como el marco de trabajo del matraz y PyJWT, que usaremos para construir el resto de la API y otros paquetes necesarios para nuestro proyecto de API.

Cree un archivo requirements.txt con los siguientes paquetes.

Flask  
datetime 
uuid
Flask-SQLAlchemy
PyJWT

Instalarlos con pip.

pip install -r requirements.txt

configurar una base de datos

Instalemos SQLite.

apt-get install sqlite3

Cree una base de datos llamada la biblioteca. Dentro de esta base de datos, crearemos dos tablas, a saber, la tabla Usuarios y Autores.

La tabla de usuarios contendr√° los usuarios registrados. Solo los usuarios registrados pueden tener acceso a la tabla de Autores.

La tabla de autores almacenará la información o los detalles de los autores, como el nombre del autor, el país de nacimiento, etc., enviados por los usuarios registrados.

Cree la base de datos usando el siguiente comando:

sqlite3 library.db

Puede comprobar si ha creado correctamente la base de datos utilizando el siguiente comando:

.databases

Abra una nueva terminal y ejecute lo siguiente en el entorno virtual que creamos anteriormente.

touch app.py

Pegue el siguiente código dentro del archivo llamado app.py

from flask import Flask, request, jsonify, make_response
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import uuid
import jwt
import datetime
from functools import wraps

La primera línea del código anterior importa paquetes como request y jsonify. Haremos uso de request para realizar un seguimiento de los datos de nivel de solicitud durante una solicitud y usaremos jsonify para generar respuestas en formato JSON.

En la línea siguiente, importamos SQLAlchemy desde el matraz_sqlalchemy para integrar las funciones de SQLAlchemy en el matraz.

Desde werkzeug.security, importamos generate_password_hash para generar un hash de contrase√Īa para los usuarios y check_password_hash para verificar la contrase√Īa del usuario al comparar la contrase√Īa enviada por los usuarios con las contrase√Īas de los usuarios almacenadas en la base de datos.

Finalmente, importamos uuid, tambi√©n conocidos como identificadores √ļnicos universales, para generar n√ļmeros de identificaci√≥n aleatorios para los usuarios.

A√ļn as√≠, dentro del archivo app.py, implemente los ajustes de configuraci√≥n para la API de la biblioteca usando el c√≥digo a continuaci√≥n dentro del archivo app.py.

Coloque el siguiente código debajo de la declaración de importación.

app = Flask(__name__)

app.config['SECRET_KEY']='Th1s1ss3cr3t'
app.config['SQLALCHEMY_DATABASE_URI']='sqlite://///home/michael/geekdemos/geekapp/library.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

db = SQLAlchemy(app)

Ahora cree dos modelos para la tabla Usuarios y Autores como se muestra a continuación. Copie y pegue el código dentro del archivo app.py.

Coloque el código debajo de la configuración de esta base de datos db = SQLAlchemy (aplicación)

class Users(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     public_id = db.Column(db.Integer)
     name = db.Column(db.String(50))
     password = db.Column(db.String(50))
     admin = db.Column(db.Boolean)
class Authors(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), unique=True, nullable=False))
     book = db.Column(db.String(20), unique=True, nullable=False))
     country = db.Column(db.String(50), nullable=False))
     booker_prize = db.Column(db.Boolean)

Generar tablas de usuarios y autores

En la terminal, escriba el siguiente código dentro del entorno virtual para generar o crear tablas para las tablas de Usuarios y Autores, como se muestra a continuación.

from app import db
db.create_all()

Luego, abra el archivo app.py dentro del entorno virtual y cree otra función.

Esta función generará tokens para permitir que solo los usuarios registrados accedan y realicen un conjunto de operaciones API en la tabla de Autores.

Coloque este código debajo del modelo de base de datos para la tabla Autores

def token_required(f):
   @wraps(f)
   def decorator(*args, **kwargs):

      token = None

      if 'x-access-tokens' in request.headers:
         token = request.headers['x-access-tokens']

      if not token:
         return jsonify({'message': 'a valid token is missing'})

      try:
         data = jwt.decode(token, app.config[SECRET_KEY])
         current_user = Users.query.filter_by(public_id=data['public_id']).first()
      except:
         return jsonify({'message': 'token is invalid'})

        return f(current_user, *args, **kwargs)
   return decorator

Crear rutas para la tabla de usuarios

Ahora vamos a crear una ruta para permitir que los usuarios se registren en la API de Autores a trav√©s de un nombre de usuario y contrase√Īa como se muestra a continuaci√≥n.

Vuelva a abrir el archivo app.py dentro del entorno virtual y pegue el siguiente código debajo de la función token_required(f)

@app.route('/register', methods=['GET', 'POST'])
def signup_user():  
 data = request.get_json()  

 hashed_password = generate_password_hash(data['password'], method='sha256')
 
 new_user = Users(public_id=str(uuid.uuid4()), name=data['name'], password=hashed_password, admin=False) 
 db.session.add(new_user)  
 db.session.commit()    

 return jsonify({'message': 'registered successfully'})

Dentro del entorno virtual, cree otra ruta en el archivo app.py para permitir que los usuarios registrados inicien sesión.

Cuando un usuario inicia sesión, se genera un token aleatorio para que el usuario acceda a la API de la biblioteca.

Pegue el código debajo de la ruta anterior que creamos.

@app.route('/login', methods=['GET', 'POST'])  
def login_user(): 
 
  auth = request.authorization   

  if not auth or not auth.username or not auth.password:  
     return make_response('could not verify', 401, {'WWW.Authentication': 'Basic realm: "login required"'})    

  user = Users.query.filter_by(name=auth.username).first()   
     
  if check_password_hash(user.password, auth.password):  
     token = jwt.encode({'public_id': user.public_id, 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=30)}, app.config['SECRET_KEY'])  
     return jsonify({'token' : token.decode('UTF-8')}) 

  return make_response('could not verify',  401, {'WWW.Authentication': 'Basic realm: "login required"'})

A√ļn as√≠, dentro del entorno virtual, cree otra ruta en el archivo app.py para obtener o recuperar todos los usuarios registrados.

Este código comprueba todos los usuarios registrados en la tabla Usuarios y devuelve el resultado final en formato JSON.

Pegue el código a continuación debajo de la ruta de inicio de sesión

@app.route('/users', methods=['GET'])
def get_all_users():  
   
   users = Users.query.all() 

   result = []   

   for user in users:   
       user_data = {}   
       user_data['public_id'] = user.public_id  
       user_data['name'] = user.name 
       user_data['password'] = user.password
       user_data['admin'] = user.admin 
       
       result.append(user_data)   

   return jsonify({'users': result})

Crear rutas para la tabla de autores.

Vamos a crear rutas para la tabla de Autores para permitir a los usuarios recuperar todos los autores en la base de datos, así como eliminar autores.

Solo los usuarios con tokens v√°lidos pueden realizar estas operaciones de API.

Dentro del archivo app.py, cree una ruta para que los usuarios registrados creen nuevos autores.

Pegue este código debajo de la ruta que le permite a un usuario recuperar todos los usuarios registrados.

@app.route('/author', methods=['POST', 'GET'])
@token_required
def create_author(current_user):
   
   data = request.get_json() 

   new_authors = Authors(name=data['name'], country=data['country'], book=data['book'], booker_prize=True, user_id=current_user.id)  
   db.session.add(new_authors)   
   db.session.commit()   

   return jsonify({'message' : 'new author created'})

A continuación, cree otra ruta para permitir que un usuario registrado con un token válido recupere todos los autores en la tabla Autores, como se muestra a continuación.

Pegue este código debajo de la ruta que permite a un usuario crear un nuevo autor.

@app.route('/authors', methods=['POST', 'GET'])
@token_required
def get_authors(current_user):

    authors = Authors.query.filter_by(user_id=current_user.id).all()

    output = []
    for author in authors:

           author_data = {}
           author_data['name'] = author.name
           author_data['book'] = author.book
           author_data['country'] = author.country
           author_data['booker_prize'] = author.booker_prize
           output.append(author_data)

     return jsonify({'list_of_authors' : output})

Finalmente, a√ļn dentro del archivo app.py, cree una ruta para eliminar un autor espec√≠fico como se muestra a continuaci√≥n.

Pegue este código debajo de la ruta que permite a un usuario recuperar una lista de autores.

@app.route('/authors/<author_id>', methods=['DELETE'])
@token_required
def delete_author(current_user, author_id):  
    author = Author.query.filter_by(id=author_id, user_id=current_user.id).first()   
    if not author:   
       return jsonify({'message': 'author does not exist'})   


    db.session.delete(author)  
    db.session.commit()   

    return jsonify({'message': 'Author deleted'})


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

Luego, guarde y cierre el archivo app.py dentro del entorno virtual.

Probando la API de la biblioteca con Postman

En esta sección, haremos uso de una herramienta de cartero para enviar una solicitud a los servicios de la base de datos. Si no tiene un cartero en su máquina, puede averiguar cómo descargarlo e instalarlo aquí.

Aparte del cartero, podemos hacer uso de otras herramientas como Curl para enviar solicitudes al servidor.

Abra una nueva terminal y escriba lo siguiente:

postman

El comando cartero har√° que su navegador web muestre la siguiente p√°gina:

postman_signup

Puede decidir registrarse y crear una cuenta gratuita, pero omitiremos y obtendremos acceso directo a la aplicación para probar la API de la biblioteca como se muestra a continuación:

Probar la API de la biblioteca

En esta secci√≥n, permitiremos que un usuario se registre en la API de la biblioteca proporcionando un nombre de usuario y una contrase√Īa √ļnica en formato JSON utilizando el m√©todo POST siguiendo los pasos a continuaci√≥n:

Haga clic en la pesta√Īa denominada Cuerpo. Luego, seleccione el bot√≥n sin procesar y elija el formato JSON. Ingrese un nombre de usuario y una contrase√Īa para registrarse como se muestra en la captura de pantalla. Al lado del bot√≥n Enviar, inserte la siguiente URL http://127.0.0.1/register. Finalmente, cambie el m√©todo a POST y presione el bot√≥n enviar.

un usuario se registra para una api

Mostrará el siguiente resultado como se muestra a continuación:

Ahora hemos registrado con éxito un usuario. Avancemos para permitir que el usuario que acaba de registrarse inicie sesión para generar un token aleatorio temporal para acceder a la tabla de Autores siguiendo los siguientes pasos:

Haga clic en la pesta√Īa de autorizaci√≥n. En la secci√≥n de tipo, seleccione autenticaci√≥n b√°sica. Luego complete el formulario de nombre de usuario y contrase√Īa con el nombre de usuario y la contrase√Īa con los que se registr√≥ anteriormente. Finalmente, presione el bot√≥n enviar para iniciar sesi√≥n y generar un token aleatorio.

Una vez que el usuario inicia sesión correctamente, se genera un token aleatorio para el usuario, como se muestra en la captura de pantalla.

Haremos uso del token aleatorio generado para acceder a la tabla de Autores.

En esta sección, agregaremos la información de un autor a la tabla Autores a través del método POST siguiendo los siguientes pasos:

Haga clic en la pesta√Īa de encabezados. Incluya los siguientes encabezados HTTP que se muestran en la captura de pantalla.

A continuaci√≥n, haga clic en la pesta√Īa del cuerpo e ingrese los detalles del nuevo autor. Luego presione el bot√≥n Enviar para agregar los detalles del autor a la tabla de autores.

También puede recuperar la información de los autores en la tabla Autores a través de lo siguiente:

Aseg√ļrese de que su token generado est√© en la secci√≥n de encabezados. si no est√° all√≠, debe llenarlo con su token. Junto al bot√≥n de enviar, ingrese esta URL http://127.0.0.1/authors Luego cambie el m√©todo HTTP a GET y presione el bot√≥n de enviar para recuperar los detalles de los autores.

Finalmente, puede eliminar el(los) autor(es) en la tabla de Autores a través del método DELETE siguiendo los siguientes pasos:

Aseg√ļrese de que su token todav√≠a est√© en la secci√≥n de encabezados. Puede verificar la pesta√Īa de encabezados para asegurarse de que la informaci√≥n necesaria est√© en su lugar. Junto al bot√≥n enviar, ingrese esta URL http://127.0.0.1/sam Luego presione el bot√≥n enviar para eliminar el usuario que especific√≥.

Puede encontrar el código fuente completo en Github. Puede clonarlo y verificarlo en su máquina.