Autenticación#
Sistema de autenticación basado en correo electrónico con soporte multiempresa.
Descripción general#
Características clave:
Correo electrónico como nombre de usuario (sin campo de nombre de usuario separado)
Backend de autenticación personalizada
Acceso multiempresa sin necesidad de volver a iniciar sesión
integración django-allauth
Tokens JWT para API
Modelos:
Modelo de usuario personalizado (
usuarios.Usuario)Basado en sesiones para web
JWT para API
Modelo de usuario personalizado#
AUTH_USER_MODEL: usuarios.Usuario
class User(AbstractUser):
username = None # Removed
email = models.EmailField(unique=True)
companies = models.ManyToManyField(Company)
active_company = models.ForeignKey(Company, null=True)
nivel_acceso = models.ForeignKey(NivelAcceso)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
Ajustes:
# construbot/config/settings/base.py
AUTH_USER_MODEL = 'users.User'
AUTHENTICATION_BACKENDS = [
'construbot.core.backends.ModelBackend', # Custom
'allauth.account.auth_backends.AuthenticationBackend',
]
Servidor de autenticación#
Archivo: construbot/core/backends.py
from django.contrib.auth.backends import ModelBackend as DjangoModelBackend
class ModelBackend(DjangoModelBackend):
"""Custom backend for email authentication"""
def authenticate(self, request, username=None, password=None, **kwargs):
# Email passed as 'username' parameter
email = kwargs.get('email', username)
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
if user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Autenticación web#
Iniciar sesión Ver#
from django.contrib.auth import authenticate, login
def login_view(request):
if request.method == 'POST':
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(request, email=email, password=password)
if user is not None:
login(request, user)
# Set active company
if not user.active_company and user.companies.exists():
user.active_company = user.companies.first()
user.save()
return redirect('dashboard')
else:
messages.error(request, 'Invalid credentials')
return render(request, 'account/login.html')
Gestión de sesiones#
Sesiones almacenadas en:
Base de datos (predeterminada):
django.contrib.sessions.backends.dbCaché (más rápido):
django.contrib.sessions.backends.cacheBase de datos en caché (recomendado):
django.contrib.sessions.backends.cached_db
Ajustes:
# Production recommended
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
SESSION_COOKIE_AGE = 1209600 # 2 weeks
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_HTTPONLY = True # No JavaScript access
Contexto de la empresa en la sesión#
# After login, active_company stored in User model
request.user.active_company # Current company
# Switch company
def switch_company(request, company_id):
company = get_object_or_404(Company, id=company_id)
if company in request.user.companies.all():
request.user.active_company = company
request.user.save()
return redirect('dashboard')
Integración Django Allauth#
Configuración:
INSTALLED_APPS = [
'allauth',
'allauth.account',
'allauth.socialaccount', # Optional
]
# Allauth settings
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
URL:
urlpatterns = [
path('accounts/', include('allauth.urls')),
]
Plantillas:
Anule todas las plantillas de autenticación en templates/account/:
iniciar sesión.htmlregistro.htmlcontraseña_reset.html
Autenticación API#
Configuración JWT#
Paquetes:
marcodjangorestdjangorestframework-simplejwt
Ajustes:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
}
URL:
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('api/v1/api-token-auth/', TokenObtainPairView.as_view()),
path('api/v1/api-token-refresh/', TokenRefreshView.as_view()),
]
Obtener fichas#
Pedido:
curl -X POST http://localhost:8000/api/v1/api-token-auth/ \\
-H "Content-Type: application/json" \\
-d '{"email":"user@example.com","password":"password"}'
Respuesta:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Usando fichas#
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLC..." \\
http://localhost:8000/api/v1/contracts/
Fichas refrescantes#
curl -X POST http://localhost:8000/api/v1/api-token-refresh/ \\
-H "Content-Type: application/json" \\
-d '{"refresh":"eyJ0eXAiOiJKV1QiLC..."}'
Gestión de contraseñas#
Hash de contraseña#
Argón2 (recomendado):
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
Requiere:
pip install argon2-cffi
Restablecer contraseña#
URL:
/accounts/password/reset/
Plantilla de correo electrónico:
Cree plantillas/cuenta/correo electrónico/contraseña_reset_key_message.txt
Fluir:
Restablecimiento de solicitudes de usuario → correo electrónico enviado
Haga clic en el enlace → ingrese una nueva contraseña
Contraseña actualizada → puede iniciar sesión
Cambiar la contraseña#
from django.contrib.auth.views import PasswordChangeView
path('accounts/password/change/', PasswordChangeView.as_view())
Mejores prácticas de seguridad#
1. Utilice HTTPS en producción:
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
2. Requisitos de contraseña fuertes:
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 8}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
3. Limitación de tarifa:
Utilice django-ratelimit o similar.
4. Autenticación de dos factores (opcional):
Utilice django-otp o django-allauth 2FA.
5. Seguridad de la sesión:
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Strict'
Solución de problemas#
«La consulta de coincidencia del usuario no existe»:
Compruebe que el correo electrónico sea correcto
Verificar que el usuario exista en la base de datos
Confirmar la configuración AUTH_USER_MODEL
«Credenciales no válidas»:
Verificar que la contraseña sea correcta
Verifique que el backend de autenticación esté configurado
Asegúrese de que el usuario esté activo (
is_active=True)
API 401 no autorizado:
El token de cheque está incluido en el encabezado
Verificar que el token no haya caducado
Confirmar formato del token:
Portador <token>
La sesión no persiste:
Verifique SESSION_COOKIE_SECURE con HTTP (debe ser Falso para local)
Verifique que el navegador acepte cookies
Verifique que el backend de la sesión esté configurado
Ver también#
Multi-inquilino - Multi-company architecture
Niveles de permiso - Permission system
../api/authentication - API auth details
../models/users - User model reference