Skip to main content

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

  1. GET /sso/login/ - El usuario hace clic en "Iniciar con Microsoft"

    • Se genera un state aleatorio (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
  2. Azure AD - El usuario se autentica con sus credenciales de Microsoft

  3. GET /sso/callback/?code=...&state=... - Azure AD redirige de vuelta

    • Se valida el state contra el guardado en sesion (anti-CSRF)
    • Se intercambia el code por tokens (POST al token endpoint con code_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 (/)
  4. 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)

  1. Ir a Azure Active DirectoryApp registrationsNew registration
  2. 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)
  3. Anotar el Application (client) IDAZURE_AD_CLIENT_ID
  4. Anotar el Directory (tenant) IDAZURE_AD_TENANT_ID
  5. Ir a Certificates & secretsNew client secret → Copiar el valor → AZURE_AD_CLIENT_SECRET
  6. Ir a API permissions → Agregar:
    • openid (delegated)
    • profile (delegated)
    • email (delegated)
  7. 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

  1. La funcion recibe el email obtenido de Azure AD
  2. Debe buscar el usuario en tu modelo de datos (puede ser el modelo User de Django u otro modelo)
  3. Debe retornar:
    • El objeto User de Django si el usuario existe y esta activo
    • None si el usuario no existe, no esta activo, o no esta vinculado
  4. 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-For para 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 userinfo
  • sso.views - Errores de Azure AD, login fallidos, excepciones
  • sso.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 requests se usa directamente en lugar de msal para mantener control total sobre el flujo y reducir dependencias.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_azure_sso-0.1.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_azure_sso-0.1.0-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

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

Hashes for django_azure_sso-0.1.0.tar.gz
Algorithm Hash digest
SHA256 52eaa9da077da6f535c81b3945ea754d570ed7255086534c1def265031077c52
MD5 4e0c8b2035c9de4abce00531186d990c
BLAKE2b-256 9d924342aa0aaf0eedd215cfa74cc2a14a1466bdb9297c76cbb2be58516d673f

See more details on using hashes here.

File details

Details for the file django_azure_sso-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_azure_sso-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 07057f0d83fa946d0a10af30fefc1d5060ba40be7f8dd6015f67afca4a2932b0
MD5 3402cfe7cb7184ec9bcd418fbccbb9a8
BLAKE2b-256 df4f8c1226c67b13d271dcde4bee5ec72b73add8cf648e9887e44382024a1505

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page