> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Métodos de autenticación

> Cuando esto se entregue, un usuario podrá entrar con contraseña, con un enlace en su correo o con la biometría de su dispositivo — y ninguno de esos caminos alcanza los datos de un member.

> Traducción. Autoritativo: [`fs-idn-0003-authentication-methods.md`](/modules/identity/features/fs-idn-0003-authentication-methods).

## Contexto

Es la configuración de Better Auth para el realm organizacional. El realm de member se configura
aparte en `core` (ADR-010), y tener las dos configuraciones en módulos distintos es lo que hace
visible la separación de realms en vez de dejarla en la memoria de alguien.

La postura que conviene enunciar: **soportamos contraseñas porque los clientes B2B las esperan**, no
porque sean buenas. Todo alrededor de ellas se endurece: chequeo contra listas de filtraciones, sin
reglas de composición que empujen a `Password1!`, y MFA encima (FS-IDN-0005) para quien tenga un
permiso sensible. Magic link y passkey existen para que un tenant que quiera evitar contraseñas
pueda.

## Alcance *(normativo)*

* Contraseña con hashing moderno y chequeo contra listas de filtraciones.
* Magic link por correo, de un solo uso y vida corta.
* Passkeys (WebAuthn) como método de primera clase.
* Verificación de correo y restablecimiento de contraseña.
* `identity.auth_accounts` vinculando cada método que un usuario tiene enrolado.
* Rate limiting y bloqueo progresivo en todo camino de credenciales.

## Fuera de alcance *(normativo)*

* Login social y SSO empresarial — FS-IDN-0008.
* MFA — FS-IDN-0005. Un segundo factor es otra preocupación que un primero.
* Emisión y duración de sesión — FS-IDN-0004.
* Autenticación de members, que vive en `core`.

## Comportamiento *(normativo)*

1. Un usuario puede tener **varios métodos a la vez**. Quitar el último se rechaza — lo dejaría fuera
   de su propia cuenta.
2. Las contraseñas se hashean con un algoritmo memory-hard con parámetros registrados en el spec y
   revisados anualmente. Guardarlas en claro o cifradas de forma reversible está PROHIBIDO, y
   registrarlas en logs también.
3. **Sin reglas de composición.** Un largo mínimo y un chequeo contra listas de filtraciones, nada
   más. Las reglas que exigen símbolo y dígito producen contraseñas predecibles y hábito de reúso; un
   chequeo de filtraciones rechaza las que efectivamente están comprometidas.
4. Los magic links y tokens de reset son de un solo uso, expiran en **15 minutos**, se guardan
   hasheados, y se invalidan cuando se emite otro para el mismo usuario.
5. Los fallos de sign-in devuelven **respuesta y tiempo idénticos** exista o no el correo.
   Distinguirlos convierte el endpoint en un oráculo de enumeración de cuentas.
6. El rate limiting es por identificador **y** por IP, con bloqueo progresivo. Un bloqueo notifica al
   usuario, porque un bloqueo que no provocó es la señal de que alguien lo está intentando.
7. Un cambio o restablecimiento de contraseña **revoca todas las demás sesiones** y notifica.
8. La verificación de correo se exige antes de que un usuario lleve cualquier rol sobre `member`.
9. Los passkeys siguen WebAuthn; un usuario puede enrolar varios y nombrar cada uno, porque un
   passkey atado a un notebook perdido debe poder quitarse desde un teléfono.
10. PROHIBIDO: cualquier credencial en un log, un payload de evento, un mensaje de error o una URL.

## Datos *(normativo)*

| Tabla                          | Invariantes clave                                                                                                                                                                                                                           |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identity.auth_accounts`       | FK `user_id`; `method` password\|magic\_link\|passkey\|oauth; `credential` hasheada donde aplique; `provider`, `provider_account_id` para oauth; `label` para passkeys; `created_at`, `last_used_at`; al menos un método activo por usuario |
| `identity.verification_tokens` | `token_hash` único; `purpose` magic\_link\|email\_verify\|password\_reset; `user_id`, `expires_at`, `consumed_at`; un solo uso                                                                                                              |

## API *(normativo)*

| Endpoint                                                 | Clase   | Auth                    | Presupuesto  |
| -------------------------------------------------------- | ------- | ----------------------- | ------------ |
| `POST /v1/identity/auth/sign-up`                         | Runtime | público, con rate limit | p95 \<300 ms |
| `POST /v1/identity/auth/sign-in`                         | Runtime | público, con rate limit | p95 \<300 ms |
| `POST /v1/identity/auth/magic-link`                      | Runtime | público, con rate limit | p95 \<300 ms |
| `POST /v1/identity/auth/passkey/{register,authenticate}` | Runtime | público / sesión        | p95 \<300 ms |
| `POST /v1/identity/auth/password/{reset-request,reset}`  | Runtime | público, con rate limit | p95 \<300 ms |
| `POST /v1/identity/auth/email/verify`                    | Runtime | token                   | p95 \<300 ms |

## Eventos *(normativo)*

Ninguno en el outbox. La autenticación se registra en `core.audit_log` con actor tipado.

## Criterios de aceptación *(normativo)*

1. Sign-in con un correo desconocido y con contraseña incorrecta son indistinguibles en cuerpo,
   estado y tiempo — verificado con un test de temporización, no por inspección.
2. Una contraseña presente en una lista de filtraciones se rechaza al registrarse y al cambiarla.
3. Un magic link no se puede usar dos veces y expira a los 15 minutos.
4. Emitir un segundo magic link invalida el primero.
5. Un restablecimiento revoca las demás sesiones y envía notificación.
6. Quitar el último método de autenticación se rechaza.
7. El bloqueo progresivo se activa ante fallos repetidos y notifica al usuario.
8. Un passkey enrolado en un dispositivo se puede quitar desde otro.
9. **Negativo:** ninguna credencial, token ni enlace aparece en logs, eventos, errores ni URLs.

## Ejecución

Un solo slice, comando síncrono. Better Auth configurado para el realm organizacional en
`backend/api`; los magic links se entregan por `messaging` mediante evento.

## Preguntas abiertas

| # | Pregunta                                                                             | Decide | Para             |
| - | ------------------------------------------------------------------------------------ | ------ | ---------------- |
| 1 | ¿Los passkeys entran en F1a, o basta contraseña más magic link? (pregunta 3 del PRD) | Daniel | antes de aprobar |
| 2 | Largo mínimo de contraseña — ¿12, o 10 con chequeo obligatorio de filtraciones?      | Daniel | antes de aprobar |

## Changelog

| Versión | Fecha      | Cambio           | Por qué | Autor                  |
| ------- | ---------- | ---------------- | ------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Borrador inicial | —       | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
