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

# Arquitectura

> Las capas de componentes, el patrón de facade del motor, el hook de token de acceso y las decisiones arquitectónicas clave detrás del IdP de B Brands.

El IdP **no** es un servidor de autorización construido desde cero. Es un delgado
facade de B Brands sobre un **motor OAuth 2.1 / OIDC administrado**, más la capa de identidad,
habilitación, auditoría y ciclo de vida que el motor no provee.

## 1. Capas de componentes

El sistema se compone de cinco capas lógicas que viven en piezas físicas
distintas.

```mermaid theme={null}
flowchart TB
    L1["<b>Layer 1 — OAuth clients</b><br/>ERP, CRM, future platforms<br/>(they authorize their own users)"]
    L2["<b>Layer 2 — Identity origin + API gateway</b> · auth.bbrands.io → api.bbrands.io<br/>OAuth proxy /api/v3/oauth/* · OIDC discovery · token exchange · Resource Server (HS256) · webhook dispatcher"]
    L3["<b>Layer 3 — Identity</b><br/>Identity admin console<br/>OAuth client registry<br/>user × platform access<br/>audit"]
    L4["<b>Layer 4 — IdP engine</b><br/>Managed OIDC engine<br/>/oauth/authorize · /oauth/token<br/>JWKS · access-token hook"]
    L5["<b>Layer 5 — Downstream</b><br/>Each client platform<br/>manages its own permissions"]

    L1 -->|HTTPS / OAuth 2.1 / OIDC| L2
    L2 --> L3
    L2 --> L4
    L2 --> L5
    L4 --> L3
```

| Capa          | Componentes                                        | Responsabilidad                                                                                               |
| ------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| 1. Clientes   | ERP, CRM, etc.                                     | Hablan OIDC, almacenan `client_id`/`secret`, renderizan la UI, **autorizan a sus propios usuarios**           |
| 2. Gateway    | API gateway                                        | La única superficie pública: proxy, redirección de consentimiento, exchange, validación de recursos, webhooks |
| 3. Identidad  | Entidades de identidad + consola de administración | Catálogo de clientes, habilitación usuario × plataforma, auditoría                                            |
| 4. Motor IdP  | Motor OIDC administrado                            | Motor del protocolo OAuth/OIDC, emisión de tokens de identidad, JWKS                                          |
| 5. Downstream | Plataformas integradas                             | Reciben el provisioning, mantienen el mapeo de identidad, definen su propia autorización                      |

<Note>
  **Lo que el IdP NO hace**: no define permisos de aplicación,
  no computa permisos efectivos, ni inyecta roles/permisos en los tokens.
  La autorización de grano fino vive en cada cliente; las APIs de B Brands autorizan
  con su RBAC interno existente.
</Note>

## 2. Cómo se organiza el código

La lógica del IdP es un módulo transversal, separado del patrón estándar por recurso
del resto de la API.

| Superficie              | Rol                                                                      |
| ----------------------- | ------------------------------------------------------------------------ |
| `/api/v3/oauth/*`       | Endpoints públicos OAuth / OIDC                                          |
| `/.well-known/*`        | Discovery + JWKS                                                         |
| Módulo IdP              | Repositorio, exchange, blacklist, gestión, webhooks, verificación JWKS   |
| Firma de token interno  | Tokens internos HS256 para el exchange                                   |
| Autorizador interno     | RBAC interno + verificación de blacklist de revocación (`jti`)           |
| Hook de token de acceso | Reescribe los claims OIDC y aplica la habilitación                       |
| `/api/v3/idp/*`         | Administración / gobernanza (clientes, accesos, auditoría, provisioning) |
| Esquema de identidad    | Modelo de datos para las entidades de identidad                          |

## 3. Decisiones arquitectónicas clave (ADR-lite)

<AccordionGroup>
  <Accordion title="ADR-01 — Un motor OAuth 2.1 administrado como motor del protocolo">
    Usamos un motor OAuth 2.1 administrado como servidor de autorización: ya es
    nuestra base de identidad, implementa el protocolo completo (PKCE, OIDC, JWKS, rotación
    de refresh), y los hooks de token de acceso personalizados nos permiten reescribir `iss`/`aud` y
    aplicar la habilitación. Contrapartida: dependemos de las capacidades del motor; la
    falta de scopes personalizados nativos es irrelevante porque no inyectamos
    permisos en los tokens.
  </Accordion>

  <Accordion title="ADR-02 — Envoltura completa de los endpoints OAuth detrás del origen de B Brands">
    Cada endpoint OAuth, OIDC, discovery y JWKS se sirve bajo un origen de B Brands.
    Los clientes nunca ven las URLs del motor, ni siquiera para el discovery o JWKS.
    Esto permite intercambiar el backend de identidad sin tocar la configuración del cliente,
    y habilita lógica personalizada (rate limiting, auditoría, transformación) en un único
    origen.
  </Accordion>

  <Accordion title="ADR-03 (revisado) — Issuer canónico = https://auth.bbrands.io (por entorno)">
    El claim `iss` de los tokens de acceso emitidos es la URL `auth.*` del entorno
    (p. ej. `https://development.auth.bbrands.io` en development), reescrita por
    el hook de token de acceso. Un token no productivo nunca es válido en producción
    porque los clientes validan el `iss` contra su propio entorno. Esto revisa
    la decisión original que nombraba a `api.*` como el issuer.
  </Accordion>

  <Accordion title="ADR-03b (revisado) — Un único origen de identidad de cara al usuario: auth.*">
    El origen `auth.*` sirve **tanto** las superficies interactivas (login,
    consentimiento) como cada endpoint del protocolo (discovery, JWKS, authorize, token,
    userinfo, revoke) — estos últimos mediante reescrituras transparentes al backend `api.*`.
    Esta es la forma de `accounts.google.com`: un origen de identidad estable
    para los clientes, mientras que el host de implementación detrás de él permanece
    reemplazable. Verificaciones de consistencia en el arranque y un monitor de disponibilidad de JWKS
    protegen el contrato.
  </Accordion>

  <Accordion title="ADR-04 — Firma asimétrica (ES256) para los tokens OAuth">
    Los JWT OAuth se firman con ES256 (ECC P-256); la clave pública se expone vía
    JWKS para que los clientes puedan validar sin compartir secretos. El JWT interno de B Brands
    sigue usando HS256 con un secreto del lado del servidor. Los dos formatos coexisten y
    nunca se mezclan.
  </Accordion>

  <Accordion title="ADR-04b — Re-firma RS256 del id_token por cliente">
    Los clientes cuya librería OIDC solo valida RS256 (p. ej. Odoo 18 con el
    módulo OCA `auth_oidc`) se registran con `id_token_alg: "RS256"` (el
    `id_token_signed_response_alg` de OIDC). Solo para esos clientes, el IdP
    verifica el `id_token` ES256 genuino y lo re-firma con su propia clave RSA,
    preservando todos los claims. El JWKS publica la clave pública RSA junto a
    la clave EC, el discovery anuncia ambos algoritmos, y el **token de acceso
    nunca se re-firma**. Cualquier fallo de re-firma vuelve al original ES256.
  </Accordion>

  <Accordion title="ADR-05 — El IdP no inyecta permisos en los tokens">
    Los tokens OAuth transportan solo claims de identidad (`sub`, `email`, perfil). No hay
    claim de permiso ni de rol. El hook de token de acceso solo reescribe `iss`/`aud` y
    verifica que el usuario esté habilitado para el cliente.
  </Accordion>

  <Accordion title="ADR-06 — Una única fuente de verdad para la identidad">
    El catálogo de clientes, la habilitación usuario × plataforma y la auditoría viven en tablas
    de gobernanza de B Brands gestionadas desde la consola de administración de identidad. El motor solo
    conserva lo que necesita para acuñar tokens.
  </Accordion>

  <Accordion title="ADR-07 — Provisioning por pre-sincronización vía webhook (no JIT)">
    Habilitar un acceso dispara un webhook que crea/actualiza el usuario en la plataforma
    de destino antes de su primer login. El payload transporta identidad y estado,
    nunca roles. La desprovisión es simétrica al suspender/revocar.
  </Accordion>

  <Accordion title="ADR-08 — TTL corto + blacklist para una revocación efectiva">
    Tokens de acceso OAuth de vida corta + un JWT de exchange de vida corta + una blacklist
    verificada en cada validación. El JWT de exchange nunca debe heredar el TTL largo
    del login tradicional.
  </Accordion>

  <Accordion title="ADR-09 — Exchange OIDC → JWT interno para consumir las APIs de B Brands">
    Un cliente que necesita llamar a las APIs de B Brands en nombre del usuario intercambia
    su token OIDC por un JWT interno de B Brands y lo usa como Bearer. El
    autorizador interno existente lo valida. Esto reutiliza el RBAC interno y
    el Resource Server sin cambios. Consulta
    [Intercambio de tokens](/es/oauth/token-exchange).
  </Accordion>
</AccordionGroup>

## 4. La pieza central: el hook de token de acceso

El hook **no** inyecta permisos. Su trabajo es reescribir `iss`/`aud`, validar
que el usuario esté habilitado para el cliente y adjuntar la dimensión de
cuenta de negocio (`https://bbrands.io/profile`). Si el usuario no está
habilitado, no se emite ningún token (denegación por defecto seguro).

```typescript theme={null}
// access-token hook (reference pseudocode)
async function accessTokenHook(request) {
  const { claims, user_id } = request;
  const clientId = claims.client_id; // from the OAuth engine

  // Non-OAuth session token: leave untouched.
  if (!clientId) return { claims };

  // OAuth token: resolve identity + enablement + account (no permissions).
  const result = await resolveOauthIdentity({ userId: user_id, clientId });

  if (!result?.enabled) {
    return { error: { http_code: 403, message: "access_denied_not_enabled" } };
  }

  return {
    claims: {
      ...claims,
      iss: ISSUER,                       // canonical per-environment auth.* issuer
      aud: AUDIENCE,                     // fixed IdP audience: "bbrands-idp"
      jti: claims.jti ?? generateJti(),  // guaranteed, drives the blacklist
      "https://bbrands.io/client_handle": result.client_handle,
      "https://bbrands.io/platform_type": result.platform_type,
      "https://bbrands.io/profile": {
        account: result.account,             // account bound via primary_user role
        odoo: result.account_external_id,    // account.external_id, null if unmapped
        user: claims.sub,
      },
    },
  };
}
```

La lógica de resolución de identidad se describe en [Modelo de datos](/es/oauth/data-model).
Modo de fallo: si el hook falla, **no se emite ningún token** — la seguridad gana.

## 5. Aislamiento por cliente

Aunque cada cliente comparte una instancia del motor, el aislamiento se garantiza mediante:

1. El **claim `https://bbrands.io/client_handle`** que identifica al cliente solicitante en cada token de acceso OAuth (`aud` es la audiencia fija `bbrands-idp`).
2. La **validación de `client_handle`** por cada cliente (un token para `crm-prod` no es válido en un cliente ERP).
3. La **habilitación** mediante el registro de acceso usuario × plataforma, verificada en la emisión, el exchange y el refresh.
4. El **registro de auditoría** filtrado por `client_handle`.

## 6. Modelo de despliegue

| Componente                                | Dónde se ejecuta                   | Notas                                  |
| ----------------------------------------- | ---------------------------------- | -------------------------------------- |
| API gateway (proxy + exchange + resource) | La plataforma de hosting de la API | Mismo despliegue que la API actual     |
| Motor OAuth                               | Servicio cloud administrado        | Tier de producción                     |
| Hook de token de acceso                   | Edge function                      | Misma región que el motor              |
| Webhook dispatcher                        | Worker / cola                      | Reintento + dead-letter                |
| Blacklist                                 | Store clave-valor de baja latencia | Intensivo en lectura, latencia crítica |
| Consola de administración de identidad    | Misma app, área de administración  | Protegida por rol de administrador     |
