Add your description here
Project description
SSO - Single Sign-On con Azure AD
Modulo de autenticacion mediante Azure Active Directory usando el protocolo OAuth2/OpenID Connect (OIDC) con soporte PKCE (Proof Key for Code Exchange).
No utiliza librerias externas de autenticacion como msal o django-allauth. La implementacion es manual sobre
requests y los endpoints estandar de Microsoft Identity Platform.
Esta es una libreria generica que puede adaptarse a cualquier proyecto Django. La forma de obtener y validar usuarios se configura via una funcion personalizada.
Estructura del modulo
src/sso/
├── __init__.py
├── apps.py # Configuracion de la app Django
├── constants.py # Mapeo de codigos de error Azure AD → mensajes en español
├── rate_limit.py # Rate limiting basado en cache de Django
├── services.py # Servicio AzureADService (OAuth2/OIDC)
├── urls.py # Rutas: /sso/login/, /sso/callback/, /sso/logout/
├── views.py # Vistas: sso_login, sso_callback, sso_logout
├── migrations/ # Vacio - el modulo no tiene modelos propios
└── tests/
├── test_services.py # Tests unitarios del servicio y PKCE
└── test_views.py # Tests de integracion de las vistas
Dependencias
| Paquete | Uso |
|---|---|
requests (2.31.0) |
Llamadas HTTP a los endpoints de Azure AD (token, userinfo) |
django.core.cache |
Backend de rate limiting |
django.contrib.auth |
Login/logout de Django |
django.contrib.sessions |
Almacenamiento de state, code_verifier y next_url |
No se usan msal, azure-identity, django-allauth ni ninguna otra libreria de autenticacion.
Flujo de autenticacion
┌──────────┐ 1. GET /sso/login/ ┌──────────────┐
│ │ ───────────────────────────→ │ │
│ │ 2. Redirect a Azure AD │ Servidor │
│ │ ←─────────────────────────── │ Django │
│ │ │ │
│ Navegador│ 3. Usuario se autentica │ │
│ │ ───────→ Azure AD ──────────→│ │
│ │ │ │
│ │ 4. GET /sso/callback/ │ │
│ │ ?code=...&state=... │ │
│ │ ───────────────────────────→ │ │
│ │ │ 5. Token │
│ │ │ exchange │
│ │ │ 6. Userinfo │
│ │ 7. Redirect a home │ 7. Login │
│ │ ←─────────────────────────── │ │
└──────────┘ └──────────────┘
Paso a paso
-
GET /sso/login/- El usuario hace clic en "Iniciar con Microsoft"- Se genera un
statealeatorio (CSRF prevention) - Se genera un par PKCE (
code_verifier+code_challenge) - Se guardan en la sesion:
sso_state,sso_code_verifier,sso_next - Se redirige al endpoint de autorizacion de Azure AD
- Se genera un
-
Azure AD - El usuario se autentica con sus credenciales de Microsoft
-
GET /sso/callback/?code=...&state=...- Azure AD redirige de vuelta- Se valida el
statecontra el guardado en sesion (anti-CSRF) - Se intercambia el
codepor tokens (POST al token endpoint concode_verifier) - Se obtiene el email del usuario desde Microsoft Graph (
/oidc/userinfo) - Se busca el usuario en la base de datos usando la funcion configurada en
SSO_GET_OR_VALIDATE_USER_METHOD - Se verifica que el usuario este activo
- Se inicia sesion en Django con
login(request, user) - Se redirige a la URL original o al home (
/)
- Se valida el
-
GET /sso/logout/- Cierra la sesion de Django- No cierra la sesion en Azure AD (el usuario sigue autenticado en Microsoft)
Configuracion
Variables de entorno
Agregar en el archivo .env:
# SSO Azure AD
SSO_ENABLED = True
AZURE_AD_TENANT_ID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_AD_CLIENT_ID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_AD_CLIENT_SECRET = tu-client-secret
AZURE_AD_REDIRECT_URI = https://tudominio.com/sso/callback/
AZURE_AD_AUTHORITY_URL = https://login.microsoftonline.com
AZURE_AD_USERINFO_URL = https://graph.microsoft.com/oidc/userinfo
| Variable | Requerida | Default | Descripcion |
|---|---|---|---|
SSO_ENABLED |
Si | False |
Habilita/deshabilita el SSO. Cuando es False, se muestra el formulario de login tradicional ademas del boton de Microsoft |
AZURE_AD_TENANT_ID |
Si | '' |
ID del tenant (directorio) de Azure AD |
AZURE_AD_CLIENT_ID |
Si | '' |
ID de la aplicacion registrada en Azure AD |
AZURE_AD_CLIENT_SECRET |
Si | '' |
Secret de la aplicacion |
AZURE_AD_REDIRECT_URI |
Si | http://localhost:8000/sso/callback/ |
URL de callback. Debe coincidir exactamente con la configurada en Azure AD |
AZURE_AD_AUTHORITY_URL |
No | https://login.microsoftonline.com |
URL base de autoridad de Microsoft |
AZURE_AD_USERINFO_URL |
No | https://graph.microsoft.com/oidc/userinfo |
Endpoint de informacion del usuario |
Configuracion en Azure AD (Portal de Azure)
- Ir a Azure Active Directory → App registrations → New registration
- Configurar:
- Name: Nombre de la aplicacion (ej:
MI app con SSO) - Supported account types: "Accounts in this organizational directory only" (Single tenant)
- Redirect URI:
https://tudominio.com/sso/callback/(tipo Web)
- Name: Nombre de la aplicacion (ej:
- Anotar el Application (client) ID →
AZURE_AD_CLIENT_ID - Anotar el Directory (tenant) ID →
AZURE_AD_TENANT_ID - Ir a Certificates & secrets → New client secret → Copiar el valor →
AZURE_AD_CLIENT_SECRET - Ir a API permissions → Agregar:
openid(delegated)profile(delegated)email(delegated)
- Hacer clic en Grant admin consent para el tenant
Comportamiento segun SSO_ENABLED
SSO_ENABLED |
Login tradicional (email/password) | Boton "Iniciar con Microsoft" |
|---|---|---|
True |
Oculto | Visible |
False |
Visible | Visible |
Para entornos de desarrollo, puede usarse (SSO_ENABLED=False), lo que disponibiliza la variuable de entorno que permite un login dual para facilitar el testing.
Funcion de obtencion y validacion de usuarios
Esta libreria es generica y no asume ninguna estructura de datos especifica. La busqueda y validacion del usuario se delegate a una funcion configurable que debe definir el proyecto que usa la libreria.
Configuracion
En el archivo settings.py de tu proyecto, agrega:
# SSO - Metodo para obtener/validar usuario
# Debe ser una funcion que reciba el email y retorne un usuario de Django o None
SSO_GET_OR_VALIDATE_USER_METHOD = 'myapp.sso_utils.get_or_validate_user'
Funcion personalizada
La funcion debe cumplir con la siguiente firma:
from django.contrib.auth import get_user_model
User = get_user_model()
def get_or_validate_user(email: str):
"""
Busca y valida un usuario en el sistema basado en el email de Microsoft.
Args:
email: Email del usuario obtenido de Azure AD (campo 'email' de /oidc/userinfo)
Returns:
User: Instancia del usuario de Django si existe y esta activo
None: Si el usuario no existe o no esta vinculado
"""
try:
# Ejemplo: buscar por campo email en modelo User
user = User.objects.get(email__iexact=email, is_active=True)
return user
except User.DoesNotExist:
return None
except User.MultipleObjectsReturned:
# Manejar caso de emails duplicados
return User.objects.filter(email__iexact=email, is_active=True).first()
Comportamiento esperado
- La funcion recibe el email obtenido de Azure AD
- Debe buscar el usuario en tu modelo de datos (puede ser el modelo User de Django u otro modelo)
- Debe retornar:
- El objeto
Userde Django si el usuario existe y esta activo Nonesi el usuario no existe, no esta activo, o no esta vinculado
- El objeto
- El login fallara con el mensaje: "Su cuenta de Microsoft no esta vinculada a ningun usuario del sistema."
Ejemplo con modelo personalizado
Si tu proyecto usa un modelo diferente para vincular usuarios (por ejemplo, Empleado, Profile, etc.):
# myapp/sso_utils.py
from django.contrib.auth import get_user_model
User = get_user_model()
def get_or_validate_user(email: str):
"""Busca usuario vinculado a traves de un modelo Empleado."""
try:
# Suponiendo un modelo Empleado con campo 'correo'
from myapp.models import Empleado
empleado = Empleado.objects.get(correo__iexact=email, activo=True)
return empleado.user # Suponiendo que Empleado tiene ForeignKey a User
except Empleado.DoesNotExist:
return None
Validacion adicional
Si necesitas validaciones adicionales (por ejemplo, verificar que el usuario pertenezca a un grupo especifico, que tenga permisos ciertos, etc.), puedes incluir esa logica dentro de la funcion personalizada.
Ejemplo con WebService externo
La funcion tambien puede realizar llamadas a un webservice externo para obtener o sincronizar el usuario:
# myapp/sso_utils.py
import requests
from django.contrib.auth import get_user_model
User = get_user_model()
def get_or_validate_user(email: str):
"""
Busca usuario via WebService externo y lo sincroniza localmente.
"""
# Llamar al webservice externo
response = requests.get(
'https://mi-sistema-externo.com/api/usuarios',
params={'email': email},
timeout=10
)
if response.status_code != 200:
return None
usuario_externo = response.json()
if not usuario_externo.get('activo'):
return None
# Sincronizar con usuario local
user, created = User.objects.get_or_create(
email=email,
defaults={
'username': usuario_externo.get('username', email.split('@')[0]),
'first_name': usuario_externo.get('nombre', ''),
'last_name': usuario_externo.get('apellido', ''),
'is_active': True,
}
)
if not created:
# Actualizar datos si el usuario ya existe
user.first_name = usuario_externo.get('nombre', user.first_name)
user.last_name = usuario_externo.get('apellido', user.last_name)
user.save()
return user
Esto permite que la libreria se integre con sistemas externos (ERP, HR, etc.) sin acoplamiento directo a modelos de Django.
Rate limiting
El modulo incluye proteccion contra intentos masivos de login.
| Parametro | Valor |
|---|---|
| Intentos maximos | 300 |
| Duracion del bloqueo | 5 minutos |
| Backend | Django cache |
La clave de rate limiting se genera combinando:
- IP del cliente (soporta
X-Forwarded-Forpara proxies) - User-Agent
- Session ID
Hasheado con SHA-256 para no exponer datos sensibles en cache.
Despues de un login exitoso, el contador se resetea automaticamente.
Seguridad
PKCE (RFC 7636)
Previene la interceptacion del codigo de autorizacion. Se genera un code_verifier aleatorio de 64 bytes y un
code_challenge con SHA-256. El verifier se envia solo en el token exchange, nunca al navegador.
State token (CSRF)
Cada flujo de login genera un token state aleatorio de 32 bytes que se valida en el callback. Previene ataques de
replay y CSRF.
Timeouts
Todas las llamadas HTTP a Azure AD tienen un timeout de 30 segundos para evitar bloqueos indefinidos.
Sesiones
Manejo de errores
Los errores de Azure AD se mapean a mensajes en español para el usuario. El mapeo esta en constants.py:
| Codigo | Mensaje |
|---|---|
AADSTS50105 |
No tiene acceso a la aplicacion. Contacte al administrador. |
AADSTS70043 |
Su sesion ha expirado. Intente iniciar sesion nuevamente. |
AADSTS53003 |
Acceso bloqueado por politicas de seguridad. |
AADSTS65004 |
El usuario rechazo el consentimiento para acceder a la aplicacion. |
AADSTS700016 |
Aplicacion no encontrada en el directorio. |
access_denied |
Acceso denegado. |
invalid_grant |
El codigo de autorizacion ha expirado o es invalido. |
server_error |
Error del servidor. Intente nuevamente mas tarde. |
Cualquier error no mapeado muestra: "Error de autenticacion. Intente nuevamente o contacte al administrador."
Todos los errores se registran en el log con nivel ERROR o WARNING.
URLs
| Metodo | URL | Vista | Descripcion |
|---|---|---|---|
| GET | /sso/login/ |
sso_login |
Inicia el flujo OAuth2. Acepta ?next=/url/ para redirect post-login |
| GET | /sso/callback/ |
sso_callback |
Callback de Azure AD. Recibe code y state |
| GET | /sso/logout/ |
sso_logout |
Cierra sesion Django. Redirige al login |
Namespace: sso. Ejemplo en templates: {% url 'sso:login' %}
Tests
Ejecutar los tests del modulo:
pytest -vvv src/sso/
Cobertura de tests
test_services.py:
- Generacion de pares PKCE (longitud, formato, unicidad)
- Mapeo de errores Azure AD a mensajes en español
- Generacion de URL de autorizacion (con y sin PKCE)
- Intercambio de codigo por tokens (exito y error)
- Obtencion de user info
- Busqueda de usuario por email
test_views.py:
- Redireccion a Azure AD con parametros correctos
- Almacenamiento de state y next_url en sesion
- Manejo de errores de Azure AD en callback
- Rechazo de state invalido (CSRF)
- Rechazo de callback sin codigo de autorizacion
- Login exitoso con usuario valido
- Rechazo de usuario inexistente
- Rechazo de usuario inactivo
- Logout y redireccion
- Rate limiting (incremento y bloqueo)
Logging
El modulo usa los siguientes loggers:
sso.services- Errores de token exchange y userinfosso.views- Errores de Azure AD, login fallidos, excepcionessso.rate_limit- Alertas de rate limiting con IP
Notas
- El modulo no tiene modelos de base de datos propios. Todo el estado se maneja via sesiones y cache de Django.
- El logout solo cierra la sesion de Django. El usuario sigue autenticado en su cuenta de Microsoft. Esto es intencional para no interferir con otras aplicaciones del tenant.
- La libreria
requestsse usa directamente en lugar demsalpara mantener control total sobre el flujo y reducir dependencias.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_azure_sso-0.1.0.tar.gz.
File metadata
- Download URL: django_azure_sso-0.1.0.tar.gz
- Upload date:
- Size: 15.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52eaa9da077da6f535c81b3945ea754d570ed7255086534c1def265031077c52
|
|
| MD5 |
4e0c8b2035c9de4abce00531186d990c
|
|
| BLAKE2b-256 |
9d924342aa0aaf0eedd215cfa74cc2a14a1466bdb9297c76cbb2be58516d673f
|
File details
Details for the file django_azure_sso-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_azure_sso-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07057f0d83fa946d0a10af30fefc1d5060ba40be7f8dd6015f67afca4a2932b0
|
|
| MD5 |
3402cfe7cb7184ec9bcd418fbccbb9a8
|
|
| BLAKE2b-256 |
df4f8c1226c67b13d271dcde4bee5ec72b73add8cf648e9887e44382024a1505
|