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