Diario de Python | #12. Color Choice: Autenticación por Token
Tags: devjournal, python, django, spanish
Published: 2023-04-12 14:36:15

El módulo de autenticación de Django ya viene incorcopado con el framework en si, y cuando lanzamos:

> python manage.py migrate

Se migran las tablas que permiten el manejo de usuarios. Esto me resuelve gran parte del trabajo de sesiones, lo cual se agradece bastante.

## Manos a la obra
Existe una clase AbstractUser, la cual podríamos heredar para generar nuestro propio usuario personalizado y agregar los campos que necesitemos. La voy a armar sin atributos, de momento, para que si en el futuro necesitamos agregar información especial al usuario, ya tengamos nuestra clase generada.

En models.py declaro el nuevo modelo User:

```python
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
```

Luego en settings.py le digo a Django que reconozca este modelo como su entidad User de su módulo de auth de la app _api_:

```python
AUTH_USER_MODEL = 'api.User'
```

Armo la migración y al ejecutarla me dice:

```
ValueError: The field admin.LogEntry.user was declared with a lazy reference to 'api.user', but app 'api' doesn't provide model 'user'.
The field api.Vote.user was declared with a lazy reference to 'api.user', but app 'api' doesn't provide model 'user'.
```

Necesito declarar a Vote, el cual tiene una clave foranea de User, que se trata de mi usuario y no el del módulo de Django. El problema es que las migraciones ya corrieron anteriormente con la relación anterior, por lo cual no me las va a tomar.

Borro mi archivo de SQLite3.db y lo recreo con las migraciones, para que Django entienda las nuevas relaciones con mi usuario personalizado.

## Auth Token y Auth User
Llegados a este punto, ya tengo un usuario personalizado, unido al módulo de autenticación de Django. Ahora voy a armar un endpoint de login en views.py, y lo voy a combinar con un componente de Auth Token que viene con DRF:

```python
@api_view(['POST'])
def user_auth(request):
    username = request.data.get('username')
    password = request.data.get('password')

    if not username or not password:
        return Response({'error': 'Se debe indicar usuario y contraseña'}, status=status.HTTP_204_NO_CONTENT)

    user = authenticate(request, username=username, password=password)
    if user is None:
        return Response({'error': 'El usuario o la contraseña son incorrectos'}, status=status.HTTP_400_BAD_REQUEST)

    token, created = Token.objects.get_or_create(user=user)
    return Response({'token': token.key}, status=status.HTTP_200_OK)
```

Luego defino la ruta en urls.py:

```python
urlpatterns += [
    path('auth', user_auth, name='api-auth')
]
```

A este método accedo mediante "POST /api/auth" con un JSON que contenga mi usuario y contraseña.

Pero, ¿qué hace mi vista user_auth?. Básicamente hace lo siguiente:
- Toma del request el user y pass. 
- Si alguno de estos valores no existe, devuelve un error.
- Luego, con la función authenticate corre el proceso de login.
- Si el login es correcto, llama al método get_or_create del modelo Token, previamente migrado por haber importado el módulo _authtoken_.
- Finalmente devuelve dicho token, que nos servirá para mantener una sesión y decirle a la API quiénes somos.

Vamos a probarlo en [Postman](https://www.postman.com/) haciendo un POST a la ruta anteriormente indicada, con un JSON Body como este:

```json
{
 "username": "admin",
 "password": "123"
}
```

Y nos devolverá lo siguiente:

![Postman POST](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2fa5ile6v5tj51ldvwj.png)

## Conclusiones
Quizá ahora la autenticación por Token parezca innecesaria, dado que no lo aplicamos a ningún endpoint, pero en el próximo capítulo van a ver la potencia de este simple string, y como impacta en nuestro objeto request.

¡Nos vemos en el próximo!