Autenticación
Authentication
SIUD soporta dos mecanismos de autenticación según el tipo de cliente: sesión Django para la interfaz web y JWT (SimpleJWT) para las APIs REST.
1. Autenticación por Sesión (Web UI)
El login basado en sesión es para usuarios que acceden a través del navegador. Devuelve una cookie de sesión que el navegador envía automáticamente en cada petición posterior.
| Propiedad | Valor |
|---|---|
| Método | GET / POST |
| Ruta | /login/ |
| Autenticación requerida | Ninguna |
| Rate limit | 5 req/minuto por IP |
Parámetros (POST form)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
username | string | Sí | Nombre de usuario |
password | string | Sí | Contraseña |
Respuestas
| Código | Descripción |
|---|---|
302 | Login exitoso — redirige a /modulos/ |
200 | Credenciales inválidas — retorna el formulario con error |
429 | Demasiados intentos — rate limit alcanzado |
Timeout de sesión
Las sesiones expiran tras 20 minutos de inactividad. El middleware de autenticación redirige automáticamente a /login/ cuando la sesión expira.
Rutas exentas del control de sesión: /back/, /api/, /api-siud/, /static/, /media/, /login/, /recuperar-password/.
2. Autenticación JWT (APIs REST)
Para integraciones externas y clientes que no usan sesión, SIUD expone endpoints JWT mediante djangorestframework-simplejwt.
Obtener tokens
| Propiedad | Valor |
|---|---|
| Método | POST |
| Ruta | /api/token/ |
| Content-Type | application/json |
| Autenticación requerida | Ninguna |
POST /api/token/
Content-Type: application/json
{
"username": "usuario@ejemplo.com",
"password": "contraseña_segura"
}
import requests
resp = requests.post(
"https://TU-DOMINIO/api/token/",
json={"username": "usuario@ejemplo.com", "password": "contraseña_segura"}
)
data = resp.json()
access = data["token"]["access"]
refresh = data["token"]["refresh"]
const { data } = await axios.post(
'https://TU-DOMINIO/api/token/',
{ username: 'usuario@ejemplo.com', password: 'contraseña_segura' }
);
const { access, refresh } = data.token;
var body = new StringContent(
JsonSerializer.Serialize(new {
username = "usuario@ejemplo.com",
password = "contraseña_segura"
}),
Encoding.UTF8, "application/json"
);
var response = await client.PostAsync("https://TU-DOMINIO/api/token/", body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://TU-DOMINIO/api/token/"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"username\":\"usuario@ejemplo.com\",\"password\":\"contraseña_segura\"}"
))
.build();
Respuesta exitosa (200):
{
"token": {
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
},
"msg": "Login success"
}
Configuración de tokens
| Token | Vida útil | Algoritmo |
|---|---|---|
| Access Token | 20 minutos | HS256 |
| Refresh Token | 1 día | HS256 |
Renovar access token
| Propiedad | Valor |
|---|---|
| Método | POST |
| Ruta | /api/token/refresh/ |
| Content-Type | application/json |
POST /api/token/refresh/
Content-Type: application/json
{ "refresh": "eyJ0eXAiOiJKV1Qi..." }
import requests
resp = requests.post(
"https://TU-DOMINIO/api/token/refresh/",
json={"refresh": refresh_token}
)
access = resp.json()["access"]
const { data } = await axios.post(
'https://TU-DOMINIO/api/token/refresh/',
{ refresh: refreshToken }
);
const newAccess = data.access;
var body = new StringContent(
JsonSerializer.Serialize(new { refresh = refreshToken }),
Encoding.UTF8, "application/json"
);
var response = await client.PostAsync(
"https://TU-DOMINIO/api/token/refresh/", body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://TU-DOMINIO/api/token/refresh/"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"refresh\":\"" + refreshToken + "\"}"
))
.build();
Response 200:
{ "access": "eyJ0eXAiOiJKV1Qi..." }
Uso del token en peticiones
Incluir el access token en el header Authorization de cada petición a la API:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Endpoints con JWT vs Sesión
Algunos endpoints tienen variantes para cada tipo de autenticación:
| Endpoint (Sesión) | Endpoint (JWT) | Descripción |
|---|---|---|
/api/ejecutar/<codigo> | /api/ejecutar-token/<codigo> | Ejecución de acciones |
3. Recuperación de contraseña
| Endpoint | Método | Descripción |
|---|---|---|
/recuperar-password/ | GET / POST | Formulario — envía email con token de recuperación |
/recuperar-password/<token>/ | GET / POST | Formulario — establece nueva contraseña con el token recibido |
Parámetros del formulario de recuperación (POST)
| Campo | Tipo | Descripción |
|---|---|---|
email | string | Email registrado del usuario |
Parámetros para reseteo de contraseña (POST)
| Campo | Tipo | Descripción |
|---|---|---|
password | string | Nueva contraseña |
password_confirm | string | Confirmación de la nueva contraseña |
4. Medidas de seguridad
| Medida | Detalle |
|---|---|
| Rate limiting | 5 intentos de login / minuto por IP (django-ratelimit) |
| Timeout de sesión | 20 minutos de inactividad (middleware) |
| CSRF | Activo en todos los endpoints de formulario |
| X-Frame-Options | SAMEORIGIN — previene clickjacking |
| Content-Type Sniff | SECURE_CONTENT_TYPE_NOSNIFF = True |
| CORS | Restringido a orígenes configurados |
| Hashing de contraseñas | PBKDF2, Argon2, BCrypt soportados |
5. Permisos en la API
La clase de permiso por defecto en DRF es IsAuthenticated. Los endpoints públicos (login, recuperación de contraseña) usan AllowAny.
El admin de Django (/back/) requiere que el usuario tenga el flag is_staff = True.
SIUD supports two authentication mechanisms depending on the client type: Django session for the web interface and JWT (SimpleJWT) for REST APIs.
1. Session Authentication (Web UI)
Session-based login is for users accessing through a browser. It returns a session cookie that the browser sends automatically on subsequent requests.
| Property | Value |
|---|---|
| Method | GET / POST |
| Path | /login/ |
| Auth required | None |
| Rate limit | 5 req/minute per IP |
Parameters (POST form)
| Field | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
password | string | Yes | Password |
Responses
| Code | Description |
|---|---|
302 | Login successful — redirects to /modulos/ |
200 | Invalid credentials — returns form with error |
429 | Too many attempts — rate limit reached |
Session timeout
Sessions expire after 20 minutes of inactivity. The auth middleware automatically redirects to /login/ when the session expires.
Paths exempt from session control: /back/, /api/, /api-siud/, /static/, /media/, /login/, /recuperar-password/.
2. JWT Authentication (REST APIs)
For external integrations and clients that don't use session, SIUD exposes JWT endpoints via djangorestframework-simplejwt.
Obtain tokens
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/token/ |
| Content-Type | application/json |
| Auth required | None |
POST /api/token/
Content-Type: application/json
{
"username": "user@example.com",
"password": "secure_password"
}
import requests
resp = requests.post(
"https://YOUR-DOMAIN/api/token/",
json={"username": "user@example.com", "password": "secure_password"}
)
data = resp.json()
access = data["token"]["access"]
refresh = data["token"]["refresh"]
const { data } = await axios.post(
'https://YOUR-DOMAIN/api/token/',
{ username: 'user@example.com', password: 'secure_password' }
);
const { access, refresh } = data.token;
var body = new StringContent(
JsonSerializer.Serialize(new {
username = "user@example.com",
password = "secure_password"
}),
Encoding.UTF8, "application/json"
);
var response = await client.PostAsync("https://YOUR-DOMAIN/api/token/", body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://YOUR-DOMAIN/api/token/"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"username\":\"user@example.com\",\"password\":\"secure_password\"}"
))
.build();
Success response (200):
{
"token": {
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
},
"msg": "Login success"
}
Token configuration
| Token | Lifetime | Algorithm |
|---|---|---|
| Access Token | 20 minutes | HS256 |
| Refresh Token | 1 day | HS256 |
Refresh access token
| Property | Value |
|---|---|
| Method | POST |
| Path | /api/token/refresh/ |
| Content-Type | application/json |
POST /api/token/refresh/
Content-Type: application/json
{ "refresh": "eyJ0eXAiOiJKV1Qi..." }
import requests
resp = requests.post(
"https://YOUR-DOMAIN/api/token/refresh/",
json={"refresh": refresh_token}
)
access = resp.json()["access"]
const { data } = await axios.post(
'https://YOUR-DOMAIN/api/token/refresh/',
{ refresh: refreshToken }
);
const newAccess = data.access;
var body = new StringContent(
JsonSerializer.Serialize(new { refresh = refreshToken }),
Encoding.UTF8, "application/json"
);
var response = await client.PostAsync(
"https://YOUR-DOMAIN/api/token/refresh/", body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://YOUR-DOMAIN/api/token/refresh/"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"refresh\":\"" + refreshToken + "\"}"
))
.build();
Response 200:
{ "access": "eyJ0eXAiOiJKV1Qi..." }
Using the token in requests
Include the access token in the Authorization header of each API request:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
3. Password recovery
| Endpoint | Method | Description |
|---|---|---|
/recuperar-password/ | GET / POST | Form — sends recovery email with token |
/recuperar-password/<token>/ | GET / POST | Form — set new password using received token |
4. Security measures
| Measure | Detail |
|---|---|
| Rate limiting | 5 login attempts / minute per IP (django-ratelimit) |
| Session timeout | 20 minutes inactivity (middleware) |
| CSRF | Active on all form endpoints |
| X-Frame-Options | SAMEORIGIN — prevents clickjacking |
| Content-Type Sniff | SECURE_CONTENT_TYPE_NOSNIFF = True |
| CORS | Restricted to configured origins |
| Password hashing | PBKDF2, Argon2, BCrypt supported |
5. API permissions
The default DRF permission class is IsAuthenticated. Public endpoints (login, password recovery) use AllowAny.
The Django admin (/back/) requires the user to have is_staff = True.