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í:
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)!
]
- Se recomienda poner esta línea la última de
urlpatternsya 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¶
from django import forms
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)#(1)!
- El «widget» nos permite cambiar el elemento HTML al renderizar el campo de formulario.
Plantilla de login¶
<form method="post" novalidate><!--(1)!-->
{% csrf_token %}
{{ form }}
<input type="submit" value="Login">
</form>
- Añadir
novalidatedesactiva las comprobaciones JavaScript para que el envío llegue al servidor y realizar el procesamiento correspondiente (gestión de errores).
Vista de login¶
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})
- No podemos llamar
logina nuestra vista ya que entraría en conflicto con la funciónloginde Django. - Indica aquí un nombre de URL a la que redirigir (por defecto).
- Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
- El formulario debe estar validado para poder continuar.
- La función
authenticatede Django verifica si las credenciales de usuario son correctas. En tal caso devuelve el usuario en cuestión. En otro caso devuelveNone. - La función
loginde Django se encarga de «logear» (iniciar sesión) al usuario. - Si todo ha ido bien redirigimos a la siguiente URL
next(si es que existe) o a la URL por defectoFALLBACK_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
Noneen el campo.
URL de login¶
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».
- 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.
Enlace de login¶
En algún punto de nuestras plantillas deberemos añadir un enlace para iniciar sesión:
- 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¶
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)
- La función
logoutde Django se encarga de cerrar la sesión del usuario actualmente «logeado».
URL de logout¶
from django.urls import path
from . import views
urlpatterns = [
path('logout/', views.user_logout, name='logout'),
]
Enlace de logout¶
En algún punto de nuestras plantillas deberemos añadir un enlace para cerrar sesión:
- 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¶
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)!
- Necesitamos modificar el widget para que no se vea la contraseña en pantalla.
- Si no ponemos esto, nos aparecerá un mensaje (largo) en el formulario indicando todos los requisitos del nombre de usuario.
- Sobreescribimos el método
save()del formulario de modelo para realizar ciertas operaciones adicionales. - Construimos el objeto
user«en memoria» con la llamada al constructor de la clase baseforms.ModelForm. - Utilizamos la función
set_passwordde Django para poder almacenar la contraseña de usuario «hasheada». - Escribimos la instancia en la base de datos.
- 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:
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¶
<form method="post" novalidate><!--(1)!-->
{% csrf_token %}
{{ form }}
<input type="submit" value="Sign up">
</form>
- Añadir
novalidatedesactiva las comprobaciones JavaScript para que el envío llegue al servidor y realizar el procesamiento correspondiente (gestión de errores).
Vista de registro¶
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})
- Si el usuario ya está autenticado lo redirigimos a una URL predefinida.
- Guardamos el formulario de modelo con lo que obtenemos una instancia de usuario.
- «Logear» al usuario tras el registro es algo opcional. Depende del contexto.
URL de registro¶
from django.urls import path
from . import views
urlpatterns = [
path('signup/', views.user_signup, name='signup'),
]
Enlace de registro¶
En algún punto de nuestras plantillas deberemos añadir un enlace para registro de usuario:
- 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:
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.