Django Rest Framework: Autenticación, Permisos, Token y ApiKey

By rogerarjona Dec. 2, 2022, 8:01 p.m. Authentication Permissions Django DRF

DRF Authentication

La Autenticación en Django Rest Framework, es la forma con la cual asociamos una solicitud/petición a una figura conocida (Usuario/Credenciales) de nuestro sistema a partir de diferentes métodos. Esto puede observarse completamente en el apartado de Authentication de DRF

La autenticación siempre se ejecuta al comienzo de la vista, antes de que se produzcan las comprobaciones de permiso y limitación, y antes de que se permita que continúe cualquier otro bloque código.

Para autenticarnos ante el API podemos usar las siguientes estrategias:

  • BasicAuthentication
  • SessionAuthentication
  • TokenAuthentication
  • OAuth

Existen paquetes de terceros que permiten identificarnos ante el sistema, o podemos incluso crear uno que se adapte a las necesidades del proyecto en cuestion.


NOTA

Recuerda que la autenticación por sí sola no permitirá ni rechazará una solicitud entrante, simplemente identifica las credenciales con las que se realizó la solicitud.


DRF Permissions

En DRF, los permisos, junto con la autenticación y la limitación se utilizan para otorgar o denegar el acceso a diferentes partes de una API. Esto en base a los criterios que uno defina, por ejemplo:

  • Version de API
  • Tipo de Usuario que hizo la peticion
  • Tipo de Token/ApiKey que se uso
  • De donde proviene la peticion.

Entendamos entonces los permisos como la "autorización".

La autenticación y la autorización trabajan en conjunto. Mientras que la autenticación es el proceso de verificar la identidad de un usuario, la autorización (permisos) es un proceso de verificación que revisa si el usuario que realizó la solicitud tiene los permisos necesarios para poder visualizar la información.


NOTA

Los permisos de DRF funcionan a nivel del Endpoint (URL/VIEW), no valida los permisos a nivel del usuario o la base de datos. Para llevar a cabo esto, se necesita crear una clase que herede de BasePermission e implementar tu propia lógica en base al usuario que realizo la petición.


Token vs APIKey

Entendiendo la diferencia entre autenticación y autorización (permisos), y que ambos pueden trabajar en conjunto para limitar una petición, expliquemos la diferencia entre Token y APIKey, la cual es muy simple:

Token

El Token siempre va ligado a la autenticación, porque tiene relación a un usuario. DRF se encarga de modificar el valor de request.user por el usuario al que el token esta ligado. Para implementarse debe hacerse lo siguiente:

1.- Agregar en la variable INSTALLED_APPS la aplicacion de authtoken

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken'
]

2.- Agregar la siguiente linea en la llave "DEFAULT_AUTHENTICATION_CLASSES", ya que es un metodo de autenticación, porque tiene relacion a un usuario.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',  # <--
    ],
}

3.- Aplicar migraciones al proyecto

./manage.py migrate

4.- Crear un token desde el shell de Django.

python manage.py drf_create_token your_username

Esto devolvera una cadena de caracteres que nos permitira hacer la petición. Si realizamos un GET sin este token, la aplicacion levantara un error.

5.- Realizamos la peticion haciendo uso de httpie(https://httpie.io/docs/cli)

http http://127.0.0.1:8000/hello/ 'Authorization: Token 9054f7aa9305e012b3c2300408c3dfdf390fcddf'

Para una mejor comprension, podemos visitar: - https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication - https://simpleisbetterthancomplex.com/tutorial/2018/11/22/how-to-implement-token-authentication-using-django-rest-framework.html

APIKey

El APIKey no suele tener relación a un usuario, por lo que sirve para delegar permisos y de esta forma, saber si puede o no acceder a una petición. Suele usarse para obtener información que no pertenece a un usuario o información que cualquiera pueda ver.

Puede utilizarse la siguiente aplicacion para generar APIKeys: https://florimondmanca.github.io/djangorestframework-api-key/

Puede implementarse rapidamente de la siguiente forma:

1.- Instalamos la dependencia en nuestro entorno.

pip install "djangorestframework-api-key==2.*"

2.- Agregamos lo siguiente en el settings.

INSTALLED_APPS = [
  # ...
  "rest_framework",
  "rest_framework_api_key",
]

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework_api_key.permissions.HasAPIKey", # <--
    ]
}

Aqui podemos observar que no agregamos nada en el apartado de DEFAULT_AUTHENTICATION_CLASSES.

3.- Aplicamos migraciones.

./manage.py migrate

4.- Generamos un APIKey desde el administrador de django, algo parecido a: 33c95ab0-0d09-44e2-b8fe-d5837b97bb1c

5.- Realizamos la peticion con httpie.

http localhost:8000/api/ 'Authorization: Api-Key 33c95ab0-0d09-44e2-b8fe-d5837b97bb1c'

Implementación Personalizada de APIKey para comportarse como un Token

Esta personalización la realizamos para el siguiente escenario:

"Un APIKey con relacion a un usuario, para poder saber quien hizo la peticion y poder hacer uso de los permisos de django"

Para ello, una vez instalada la dependencia, podemos realizarlo siguiente:

1.- Crear tu propio modelo de APIKey con relacion a usuario. Para ello debemos heredar de AbstractAPIKey

from rest_framework_api_key.models import AbstractAPIKey

class ApiKey(AbstractAPIKey):
    user = models.ForeignKey(
        'api.User',
        on_delete=models.CASCADE,
    )

2.- Agregamos en el admin de Django, el nuevo modelo:

from rest_framework_api_key.admin import APIKeyModelAdmin  # <--

class APIKeyModelAdmin(APIKeyModelAdmin):
    model = ApiKey
    list_display = APIKeyModelAdmin.list_display + ("user",)
    search_fields = APIKeyModelAdmin.search_fields + ("user",)


admin.site.register(ApiKey, APIKeyModelAdmin)

3.- Creamos un nuevo archivo de permisos donde heredamos de HasAPIKey. Esto para que podamos asignar el usuario al request, y poder manipularlo usando "request.user". La intención de esto, es que podamos validar si el usuario cuenta con los permisos de Django correspondientes a la vista.

from rest_framework_api_key.permissions import BaseHasAPIKey

class HasAPIKey(BaseHasAPIKey):
    model = ApiKey

    def has_view_permission(self, request, view, permissions=[]):  # permissions es la lista de permisos a validar
        """
        :type request: django.http.HttpRequest
        :type view: Any
        :type permissions: list
        :return: bool
        """

        key = self.get_key(request)
        if not key:
            return False

        is_valid, api_key = self.model.objects.validate_and_get_key(key) 
        if not is_valid:
            raise PermissionDenied(detail="No cuenta con los permisos para realizar esta accion")

        request.user = api_key.owner

        if not api_key.owner.has_perms(permissions):
            raise PermissionDenied(detail="No cuenta con los permisos para realizar esta accion")

        return is_valid

Como se puede apreciar, si el usuario no cuenta con los permisos de la vista, se levantara el error de Permiso Denegado y no podra consultar el Endpoint.