Saltar a contenido

Autenticación

Django intermedio

Django proporciona un sistema de autenticación —incorporado en el propio framework— que facilita en gran medida la gestión de accesos y de usuarios.

Usuario

Existe un modelo User que ya viene predefinido en Django. A continuación se muestran sus principales características:

Campo Descripción Modo
username Nombre de usuario
password Contraseña
first_name Nombre
last_name Apellido(s)
email Correo electrónico
Campo Descripción Modo
is_staff Establece si el usuario tiene acceso a la interfaz administrativa
is_active Establece si el usuario puede logearse
is_superuser Establece si el usuario es superusuario
last_login Fecha y hora del último inicio de sesión
date_joined Fecha y hora de la creación de la cuenta
is_authenticated Indica si el usuario está autenticado
set_password() Establece la contraseña del usuario
check_password() Comprueba la contraseña del usuario
Password

El password se almacena «hasheado» en la base de datos. Django utiliza —por defecto— el algoritmo PBKDF2 con una función «hash» SHA256, aunque se podría cambiar. El formato utilizado es: <algorithm>$<iterations>$<salt>$<hash>

Un ejemplo de «password» en la base de datos sería:

>>> user.password
'pbkdf2_sha256$1000000$Y9opN6ZKNLMm9E1vAPrBzi$dNDnlbmN+phNora6ZUnok05NTH7BEgWjpg/mqulk3Yw='

Django incorpora una serie de validadores para la contraseña que aseguran su fortaleza.

Acceso al modelo

Este modelo se encuentra en la clase django.contrib.auth.models.User, pero es posible «suplantarla» con un modelo propio de usuario. Es por ello que no se recomienda acceder directamente a esta clase a través de un import sino utilizar otros «atajos» más genéricos.

Para acceder al modelo de usuario disponemos de dos vías:

Para uso en... Importación Acceso Descripción Valor por defecto
Claves ajenas from django.conf import settings settings.AUTH_USER_MODEL Cadena de texto cualificada 'auth.User'
Vistas y/o formularios from django.contrib.auth import get_user_model get_user_model() Función que devuelve el modelo django.contrib.auth.models.User

Acceso a la instancia

Para acceder a la instancia del usuario logeado en Django disponemos de dos vías:

Para uso en... Acceso
Vistas request.user
Plantillas {{ user }}

Veamos un ejemplo en el que accedemos a la instancia de un usuario a través del modelo:

>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()

>>> guido = User.objects.get(username='guido')

>>> guido
<User: guido>
>>> guido.first_name
'Guido'
>>> guido.last_name
'van Rossum'

Usuario anónimo

Cuando el usuario que interactúa con la web aún no está autenticado, Django lo identifica como AnonymousUser.

Aplicación

Se recomienda crear una aplicación accounts (o similar) donde implementar todos los artefactos necesarios para la autenticación de usuarios.

En las URLs de primer nivel deberíamos incluir algo así:

main/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('posts/', include('posts.urls')),
    path('', include('accounts.urls')),#(1)!
]

  1. Se recomienda poner esta línea la última de urlpatterns ya que casa con cualquier patrón y al ponerla en otro lugar invalidaría el resto de rutas.

De esta forma tendremos acceso «directo» a las funcionalidades de autenticación desde la raíz de la URL:

  • http://misitioweb.com/login/
  • http://misitioweb.com/logout/
  • http://misitioweb.com/signup/

Nombre

No podemos llamar auth a esta aplicación porque entraría en conflicto con la aplicación django.contrib.auth de Django.

Login

Para implementar el procedimiento de inicio de sesión debemos desarrollar varios elementos:

  • Formulario.
  • Plantilla.
  • Vista.
  • URL.
  • Enlace.

Formulario de login

accounts/forms.py
from django import forms


class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)#(1)!

  1. El «widget» nos permite cambiar el elemento HTML al renderizar el campo de formulario.

Plantilla de login

accounts/templates/accounts/login.html
<form method="post" novalidate><!--(1)!-->
  {% csrf_token %}
  {{ form }}
  <input type="submit" value="Login">
</form>

  1. Añadir novalidate desactiva las comprobaciones JavaScript para que el envío llegue al servidor y realizar el procesamiento correspondiente (gestión de errores).

Vista de login

accounts/views.py
from django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render

from .forms import LoginForm


def user_login(request):#(1)!
    FALLBACK_REDIRECT = 'index'#(2)!

    if request.user.is_authenticated:#(3)!
        return redirect(FALLBACK_REDIRECT)
    if request.method == 'POST':
        if (form := LoginForm(request.POST)).is_valid():#(4)!
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            if user := authenticate(request, username=username, password=password):#(5)!
                login(request, user)#(6)!
                return redirect(request.GET.get('next', FALLBACK_REDIRECT))#(7)!
            else:
                form.add_error(None, 'Incorrect username or password.')#(8)!
    else:
        form = LoginForm()
    return render( request, 'accounts/login.html', {'form': form})

  1. No podemos llamar login a nuestra vista ya que entraría en conflicto con la función login de Django.
  2. Indica aquí un nombre de URL a la que redirigir (por defecto).
  3. Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
  4. El formulario debe estar validado para poder continuar.
  5. La función authenticate de Django verifica si las credenciales de usuario son correctas. En tal caso devuelve el usuario en cuestión. En otro caso devuelve None.
  6. La función login de Django se encarga de «logear» (iniciar sesión) al usuario.
  7. Si todo ha ido bien redirigimos a la siguiente URL next (si es que existe) o a la URL por defecto FALLBACK_REDIRECT, en otro caso.
    • Añadimos un error al formulario indicando que las credenciales son incorrectas.
    • El primer parámetro de la función add_error() es el campo al que queremos añadir el error, y el segundo parámetro es el mensaje de error en sí mismo.
    • Como estos errores son «generales» a todo el formulario, indicamos None en el campo.

URL de login

accounts/urls.py
from django.urls import path

from . import views

# No definimos nombre de aplicación porque queremos que las
# URLs de autenticación sean globales (sin espacio de nombres).
# app_name = 'accounts'

urlpatterns = [
    path('login/', views.user_login, name='login'),
]

LOGIN_URL

Es importante definir en el fichero settings.py la variable LOGIN_URL que le indica a Django a qué URL debe redirigir al usuario para hacer «login».

main/settings.py
LOGIN_URL = 'login'#(1)!

  1. Vale tanto una URL como una URL nombrada.

Da igual el «lugar» en el que pongamos estas variables dentro de settings.py. Una buena práctica sería agrupar aquellas configuraciones que tengan relación.

En algún punto de nuestras plantillas deberemos añadir un enlace para iniciar sesión:

<a href="{% url 'login' %}">Login</a><!--(1)!-->

  1. Puedes usar condiciones para saber si el usuario está autenticado {% if user.is_authenticated %}.

Logout

Para implementar el procedimiento de cierre de sesión debemos desarrollar varios elementos:

  • Formulario.
  • Plantilla.
  • Vista.
  • URL.
  • Enlace.

Vista de logout

accounts/views.py
from django.contrib.auth import logout
from django.shortcuts import redirect


def user_logout(request):
    FALLBACK_REDIRECT = 'index'

    logout(request)#(1)!
    return redirect(FALLBACK_REDIRECT)

  1. La función logout de Django se encarga de cerrar la sesión del usuario actualmente «logeado».

URL de logout

accounts/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('logout/', views.user_logout, name='logout'),
]

En algún punto de nuestras plantillas deberemos añadir un enlace para cerrar sesión:

<a href="{% url 'logout' %}">Logout</a><!--(1)!-->

  1. Puedes usar condiciones para saber si el usuario está autenticado {% if user.is_authenticated %}.

Registro

Para implementar el procedimiento de registro de usuario debemos desarrollar varios elementos:

  • Formulario.
  • Plantilla.
  • Vista.
  • URL.
  • Enlace.

Formulario de registro

accounts/forms.py
from django import forms
from django.contrib.auth import get_user_model


class SignupForm(forms.ModelForm):
    class Meta:
        model = get_user_model()
        fields = ('username', 'password', 'first_name', 'last_name', 'email')
        widgets = {'password': forms.PasswordInput}#(1)!
        help_texts = {'username': None}#(2)!

    def save(self, *args, **kwargs):#(3)!
        user = super().save(commit=False)#(4)!
        user.set_password(self.cleaned_data['password'])#(5)!
        user = super().save(*args, **kwargs)#(6)!
        return user#(7)!

  1. Necesitamos modificar el widget para que no se vea la contraseña en pantalla.
  2. Si no ponemos esto, nos aparecerá un mensaje (largo) en el formulario indicando todos los requisitos del nombre de usuario.
  3. Sobreescribimos el método save() del formulario de modelo para realizar ciertas operaciones adicionales.
  4. Construimos el objeto user «en memoria» con la llamada al constructor de la clase base forms.ModelForm.
  5. Utilizamos la función set_password de Django para poder almacenar la contraseña de usuario «hasheada».
  6. Escribimos la instancia en la base de datos.
  7. El método save() de un formulario siempre debe devolver la instancia creada.
Campos requeridos

Cuando implementamos un formulario de modelo los campos requeridos se infieren directamente desde el propio modelo. Pero hay ocasiones en las que no tenemos acceso directo al modelo, como es el caso que nos ocupa donde estamos utilizando el modelo User que proporciona Django.

El modelo User sólo define como requeridos el nombre de usuario y la contraseña. Si quisiéramos añadir otros atributos como requeridos tendríamos que implementar otra estrategia. A continuación se muestra el código del formulario añadiendo un atributo required donde indicamos los campos requeridos y son modificamos en el constructor de la clase:

accounts/forms.py
from django import forms
from django.contrib.auth import get_user_model


class SignupForm(forms.ModelForm):
    class Meta:
        model = get_user_model()
        fields = ('username', 'password', 'first_name', 'last_name', 'email')
        required = ('username', 'password', 'first_name', 'last_name', 'email')
        widgets = {'password': forms.PasswordInput}
        help_texts = {'username': None}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for field in self.Meta.required:
            self.fields[field].required = True

    def save(self, *args, **kwargs):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data['password'])
        user = super().save(*args, **kwargs)
        return user

Plantilla de registro

accounts/templates/accounts/signup.html
<form method="post" novalidate><!--(1)!-->
  {% csrf_token %}
  {{ form }}
  <input type="submit" value="Sign up">
</form>

  1. Añadir novalidate desactiva las comprobaciones JavaScript para que el envío llegue al servidor y realizar el procesamiento correspondiente (gestión de errores).

Vista de registro

accounts/views.py
from django.contrib.auth import login
from django.shortcuts import redirect, render

from .forms import SignupForm


def user_signup(request):
    FALLBACK_REDIRECT = 'index'

    if request.user.is_authenticated:#(1)!
        return redirect(FALLBACK_REDIRECT)
    if request.method == 'POST':
        if (form := SignupForm(request.POST)).is_valid():
            user = form.save()#(2)!
            login(request, user)#(3)!
            return redirect(FALLBACK_REDIRECT)
    else:
        form = SignupForm()
    return render(request, 'accounts/signup.html', {'form': form})

  1. Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
  2. Guardamos el formulario de modelo con lo que obtenemos una instancia de usuario.
  3. «Logear» al usuario tras el registro es algo opcional. Depende del contexto.

URL de registro

main/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('signup/', views.user_signup, name='signup'),
]

En algún punto de nuestras plantillas deberemos añadir un enlace para registro de usuario:

<a href="{% url 'signup' %}">Sign up</a><!--(1)!-->

  1. Puedes usar condiciones para saber si el usuario está autenticado {% if user.is_authenticated %}.

Requisito estar logeado

Si necesitamos que ciertas vistas de nuestra aplicación sean accesibles únicamente para usuarios logeados podemos hacer uso del decorador @login_required que nos proporciona Django.

Supongamos por ejemplo que una persona sólo puede escribir un «post» en nuestro «blog» si está previamente logeada:

posts/views.py
from django.contrib.auth.decorators import login_required


@login_required
def add_post(request):
    # rest of the view code here

Redirección para login

Si un usuario no logeado trata de acceder a una vista «protegida», será redirigido a la URL de «login» que venga especificada en la variable LOGIN_URL del fichero settings.py.