> ## 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.

# Integración de clientes

> Guía paso a paso para integrar una plataforma como cliente OAuth, usando un ERP genérico como implementación de referencia.

Esta guía acompaña a un integrador en la conexión de una plataforma con el IdP de B Brands.
Se usa un ERP genérico como cliente de referencia (first-party), pero los pasos
se generalizan a cualquier plataforma OIDC.

<Info>
  El IdP autentica a tus usuarios. **Tu plataforma gestiona su propia
  autorización** (roles/grupos). El IdP nunca envía roles ni permisos.
</Info>

## Resumen de la integración

| Aspecto                         | Decisión                                                   |
| ------------------------------- | ---------------------------------------------------------- |
| Flujo                           | Authorization Code + PKCE con OIDC                         |
| Mapeo de identidad              | ID token `sub` → el id de usuario externo de tu plataforma |
| Provisioning                    | Pre-sincronización vía webhook (identidad + estado)        |
| Autorización en tu app          | **Tu responsabilidad**                                     |
| Consumo de las APIs de B Brands | Con usuario → intercambio de tokens; sin usuario → API key |

## 1. Registra el cliente OAuth (lado B Brands)

Crea el cliente vía la consola de administración de identidad o la management API. Registra un
**cliente distinto por entorno** (`<platform>-dev`, `<platform>-cert`,
`<platform>-prod`).

```http theme={null}
POST /api/v3/idp/clients
Content-Type: application/json

{
  "client_handle": "erp-prod",
  "display_name": "ERP (Production)",
  "platform_type": "erp",
  "trust_level": "first_party",
  "redirect_uris": ["https://your-erp.example.com/auth/callback"],
  "owner_team": "erp-team",
  "provisioning_webhook_url": "https://your-erp.example.com/internal/idp/provision",
  "access_token_ttl_seconds": 1800
}
```

La respuesta devuelve el `client_secret` y el secreto del webhook de provisioning
**una sola vez** — guárdalos en tu secret manager. Luego habilita usuarios piloto:

```http theme={null}
POST /api/v3/idp/access
{ "user_id": "<uuid>", "client_handle": "erp-prod" }
```

## 2. Configura el cliente (lado de tu plataforma)

Usando el soporte OIDC de tu plataforma, apunta la autoconfiguración al discovery y
proporciona Client ID + Secret:

* URL de discovery: `https://auth.bbrands.io/.well-known/openid-configuration`
* Client ID: `<UUID devuelto en el onboarding>`
* Client Secret: `<el que devolvió el IdP>`

URL base por entorno (issuer):

| Entorno       | URL base / issuer                       |
| ------------- | --------------------------------------- |
| Development   | `https://development.auth.bbrands.io`   |
| Certification | `https://certification.auth.bbrands.io` |
| Production    | `https://auth.bbrands.io`               |

Para la configuración manual (se muestran los valores de producción):

| Campo                        | Valor (producción)                                                                    |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| Authorization URL            | `https://auth.bbrands.io/api/v3/oauth/authorize`                                      |
| Token URL                    | `https://auth.bbrands.io/api/v3/oauth/token`                                          |
| User Info URL                | `https://auth.bbrands.io/api/v3/oauth/userinfo`                                       |
| JWKS URL                     | `https://auth.bbrands.io/.well-known/jwks.json`                                       |
| Scope                        | `openid email profile`                                                                |
| PKCE                         | Obligatorio, solo `S256`                                                              |
| Algoritmo de firma del token | `ES256` (por defecto) o `RS256` si tu cliente se registró con `id_token_alg: "RS256"` |
| Issuer                       | `https://auth.bbrands.io`                                                             |

<Warning>
  Usa un cliente distinto por entorno. Un token no productivo **nunca** debe ser
  válido en producción porque el `iss` difiere.
</Warning>

<Note>
  Valida el **token de acceso** (issuer `auth.*`, `aud` = `bbrands-idp`,
  `https://bbrands.io/client_handle` = tu handle) como la aserción de identidad.
  El `id_token` se firma con la misma clave ES256 pero conserva el issuer
  interno del motor, así que no fijes la verificación de issuer de tu librería a él — consulta
  [Endpoints](/es/oauth/endpoints#token-endpoint).
</Note>

<Note>
  **Si tu librería OIDC solo valida id\_tokens RS256** (p. ej. Odoo 18 con el
  módulo OCA `auth_oidc`): solicita que tu cliente se registre con
  `id_token_alg: "RS256"`. Tu `id_token` se entrega entonces firmado como
  RS256 (`kid` resoluble vía la JWKS URL de arriba, mismos claims), sin cambios
  de tu lado más allá de la configuración estándar. El token de acceso sigue
  siendo ES256.
</Note>

### Valida la configuración

1. Visita tu página de login — el botón "Iniciar sesión con B Brands" debería aparecer.
2. Haz clic en él → deberías ser redirigido a `…auth.bbrands.io/api/v3/oauth/authorize?…`.
3. Tras `authorize`, el navegador se redirige a `…auth.bbrands.io/oauth/consent?…` (mismo origen) y, sin una sesión, primero al login de B Brands.
4. Con una sesión de B Brands activa y un usuario habilitado → autoaprobación → de vuelta a tu plataforma, con sesión iniciada.

## 3. Mapeo de identidad

| Concepto                | B Brands                             | Tu plataforma                          |
| ----------------------- | ------------------------------------ | -------------------------------------- |
| Id de usuario           | UUID del usuario                     | Tu id de usuario interno               |
| Email                   | `email`                              | Tu campo de login/email                |
| Enlace externo          | —                                    | Id externo = ID token `sub`            |
| Cuenta de negocio       | `https://bbrands.io/profile.account` | Tu registro de cliente/tenant          |
| Id externo de la cuenta | `https://bbrands.io/profile.odoo`    | Tu propio id de registro (p. ej. Odoo) |

En cada login, busca al usuario por (`provider`, id externo = `sub`). Comportamiento
recomendado cuando no se encuentra: **rechazar** con "solicita acceso a tu administrador" (los usuarios
deben estar aprovisionados previamente). La alternativa JIT (autocrear) es solo para flujos
donde la pre-sincronización es imposible.

<Note>
  Tanto el access token como la respuesta de UserInfo incluyen el objeto
  `https://bbrands.io/profile`: `account` es la cuenta de B Brands vinculada al
  usuario mediante el rol `primary_user`, `odoo` es el id del sistema externo de
  esa cuenta (`null` hasta que la API de migración la mapee) y `user` es igual a
  `sub`. Úsalo para enlazar el login con tu propio registro de cliente sin
  llamadas adicionales a la API.
</Note>

## 4. Webhook de provisioning

Expón un endpoint que el IdP llama cuando el acceso cambia. Verifica la firma HMAC,
luego crea/actualiza o desactiva al usuario. **Asigna tus propios grupos —
el IdP no envía roles.**

```python theme={null}
# Framework-agnostic handler (pseudocode)
def provision(request):
    signature = request.headers.get("X-BBrands-Signature")
    body = request.raw_body
    secret = get_config("idp.webhook_secret")
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        return 401, {"error": "invalid_signature"}

    payload = json.loads(body)
    if payload["event_type"] == "user_provisioned":
        upsert_user(payload["user"])  # assign your default group
    elif payload["event_type"] in ("user_deprovisioned", "user_suspended"):
        deactivate_user(payload["user"]["sub"])
    return 200, {"ok": True}
```

### Payload del webhook

Headers: `X-BBrands-Signature` (HMAC-SHA256 en hex del cuerpo),
`X-BBrands-Event-Id`, `X-BBrands-Delivery-Attempt`.

```json theme={null}
{
  "event_id": "uuid",
  "event_type": "user_provisioned",
  "client_handle": "erp-prod",
  "user": {
    "sub": "uuid-of-bbrands-user",
    "email": "user@example.com",
    "name": "Jane Doe",
    "given_name": "Jane",
    "family_name": "Doe"
  },
  "occurred_at": "2026-06-15T10:00:00Z"
}
```

Respuestas esperadas: `2xx` → `delivered`; `4xx` → `failed` (sin reintento, p. ej. firma
inválida); `5xx`/timeout → reintento con backoff exponencial, luego `dead_letter` + alerta
al administrador. Usa `event_id` para la idempotencia frente a los reintentos.

## 5. Consumo de las APIs de B Brands (con usuario)

Cuando actúas en el contexto de un usuario con sesión iniciada, intercambia el token OIDC por un
JWT interno de B Brands y úsalo como Bearer. Detalles y manejo de errores en
[Intercambio de tokens](/es/oauth/token-exchange).

```python theme={null}
r = requests.post(
    'https://api.bbrands.io/api/v3/oauth/exchange',
    json={'subject_token': id_token, 'subject_token_type': 'id_token'},
    timeout=10,
)
token = r.json()['data']['token']  # internal B Brands JWT, short TTL

requests.get(
    'https://api.bbrands.io/api/v3/account/account',
    headers={'Authorization': f'Bearer {token}'},
    timeout=30,
)
```

## 6. Máquina a máquina

Un cron de sincronización nocturna **no tiene un usuario interactivo**, así que no usa OAuth ni el
exchange. Usa una **API key** existente de B Brands, aprovisionada con least
privilege.

```python theme={null}
requests.get(
    'https://api.bbrands.io/api/v3/account/account',
    headers={'X-API-Key': api_key},
    params={'page': 1, 'itemsPerPage': 200},
    timeout=30,
)
```

## 7. Casos límite

| Caso                                                        | Manejo                                                                                  |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Usuario con sesión en el cliente, deshabilitado en B Brands | El próximo refresh OAuth falla → el cliente lo cierra (mantén el TTL bajo)              |
| El webhook llega antes del primer login                     | El usuario se crea sin contraseña (forzado a OIDC)                                      |
| Cambio de email                                             | `sub` es estable, el email no; el upsert actualiza el email                             |
| Usuario reactivado                                          | El evento `user_reactivated` restaura el estado activo (los grupos los conserva tu app) |
| Webhook duplicado (reintento)                               | Idempotente vía `event_id`                                                              |
| El JWT de exchange expira a mitad de la operación           | Vuelve a intercambiar y reintenta una vez                                               |

## 8. Checklist de puesta en producción

<Steps>
  <Step title="Módulo instalado">Módulo de integración instalado en producción.</Step>
  <Step title="Proveedor OIDC probado">Proveedor configurado y verificado en staging.</Step>
  <Step title="Secretos almacenados">Secreto del webhook y client secret almacenados cifrados.</Step>
  <Step title="API key aprovisionada">Para el cron, con permisos mínimos.</Step>
  <Step title="Exchange probado">OIDC → JWT interno probado para los flujos con contexto de usuario.</Step>
  <Step title="Política de grupos definida">La política de asignación de grupos de tu plataforma está definida.</Step>
  <Step title="Lista de pre-provisioning validada">Lista de usuarios validada con Personas/RR. HH.</Step>
  <Step title="Runbooks y alertas">Runbook de "No puedo autenticarme" publicado; alertas de webhook/login configuradas.</Step>
</Steps>
