Ir al contenido

Iniciar sesión con Rhombus

Agrega Iniciar sesión con Rhombus a tu aplicación con OAuth 2.0 y PKCE: registra un cliente, solicita scopes, gestiona códigos de autorización, access tokens y refresh tokens, y llama a la API con un token Bearer.

En esta página

Descripción general

Rhombus soporta OAuth 2.0 con PKCE para que puedas crear aplicaciones que inicien sesión con las credenciales existentes de Rhombus. Cuando un usuario hace clic en Iniciar sesión con Rhombus en tu aplicación, es redirigido a la Consola de Rhombus para autenticarse, y luego es devuelto a tu URI de redirección con un código de autorización de corta duración. Intercambias ese código por un access token y llamas a la API de Rhombus en nombre del usuario.

Este es el mismo flujo utilizado por el Rhombus CLI oficial: rhombus login es una implementación de referencia funcional en Go que puedes leer de principio a fin.

Usa esta guía para construir:

  • Herramientas CLI que se autentican mediante inicio de sesión por navegador (como el propio Rhombus CLI)
  • Aplicaciones web que permiten a los usuarios de Rhombus iniciar sesión en tu servicio
  • Paneles de administración e integraciones internas para clientes que gestionan muchas organizaciones de Rhombus
  • Aplicaciones de escritorio usando una redirección por loopback local

Cómo funciona el flujo

La superficie OAuth de Rhombus abarca tres hosts. Esta es una fuente común de confusión: tu aplicación se comunica con los tres en distintas etapas del flujo.

RolHost de EE. UU.Host de la UE
Donde el usuario inicia sesión y aprueba tu aplicaciónconsole.rhombussystems.comconsole.eu.rhombussystems.com
Endpoints de tokens, refresh, revocación y registro dinámicoauth-web.rhombussystems.comauth-web.eu.rhombussystems.com
Llamadas a la API realizadas con el access token resultanteapi2.rhombussystems.comapi2.eu.rhombussystems.com
Cargando el diagrama…

Antes de comenzar

Información

Antes de comenzar, asegúrate de tener:

  • Una cuenta de Rhombus, más una clave de API (generada en la Consola de Rhombus en Configuración → Integraciones y recursos para desarrolladores → Tokens de API) si registras tu aplicación con la API; el registro dinámico de clientes no necesita clave
  • Una URI de redirección que tu aplicación controle: una URL HTTPS pública en producción, o http://localhost:<port>/callback para aplicaciones CLI y de escritorio
  • Familiaridad básica con el flujo de código de autorización OAuth 2.0 y PKCE (RFC 7636)

Paso 1: Registra tu aplicación

Antes de poder iniciar un flujo OAuth, Rhombus necesita conocer tu aplicación. El registro te entrega un clientId y, para un cliente confidencial, un clientSecret. Un cliente público (una CLI o una app de escritorio que no puede guardar un secreto) se registra con tokenEndpointAuthMethod: "none" y no recibe ningún secreto.

Hay dos formas de obtener un clientId:

Registrarse con la API

POST /api/oauth/submitApplication devuelve un clientId y clientSecret nuevos. Almacena el clientSecret de forma segura: no se puede recuperar más tarde.

import requests

headers = {
    "x-auth-scheme": "api-token",
    "x-auth-apikey": "YOUR_API_KEY",
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api2.rhombussystems.com/api/oauth/submitApplication",
    headers=headers,
    json={
        "name": "Acme Console",
        "description": "Acme internal dashboard for Rhombus operators",
        "contactEmail": "engineering@acme.example",
        "redirectUri": "https://acme.example/oauth/callback",
    },
)

data = response.json()
print("clientId:    ", data["clientId"])
print("clientSecret:", data["clientSecret"])  # store securely — not retrievable later

Ejemplo de respuesta:

json
{
  "clientId": "PJjjlcKAQPCzIcaeprzEVg",
  "clientSecret": "YOUR_CLIENT_SECRET"
}
Consejo

Hay endpoints adicionales disponibles para gestionar aplicaciones registradas: getAllApplicationsForOrg, getApplicationByClientId, updateApplication y deleteApplication. Consulta la Referencia de la API bajo la etiqueta OAuth. Eliminar una aplicación revoca todas las autorizaciones que los usuarios le dieron.

Registro dinámico de clientes

Una aplicación también puede registrarse sola con el registro dinámico de clientes de OAuth 2.0 (RFC 7591): envía un cuerpo JSON a https://auth-web.rhombussystems.com/oauth/register, sin clave de API. Registra un cliente público ("token_endpoint_auth_method": "none") cuando la aplicación no pueda guardar un secreto; en ese caso depende solo de PKCE.

cURLbash
curl -X POST "https://auth-web.rhombussystems.com/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Acme CLI",
    "redirect_uris": ["http://localhost:8765/callback"],
    "token_endpoint_auth_method": "none"
  }'

La respuesta incluye el nuevo client_id, y un client_secret salvo que hayas registrado un cliente público. Guarda el client_id y reutilízalo en los siguientes inicios de sesión en lugar de registrarte de nuevo.

El registro anónimo está limitado a 10 registros por hora desde una misma dirección IP de cliente y a 100 por hora en total. Si se supera cualquiera de los dos límites, el endpoint devuelve 429 Too Many Requests con un encabezado Retry-After y un cuerpo de error de OAuth cuyo error es temporarily_unavailable; espera los segundos que indica Retry-After antes de volver a intentarlo.

Paso 2: Construye la URL de autorización

Rhombus usa PKCE (Proof Key for Code Exchange) para protegerse contra la interceptación de códigos de autorización. Para cada intento de inicio de sesión, genera:

  • Un code verifier: una cadena aleatoria URL-safe de 43 a 128 caracteres
  • Un code challenge: el hash SHA-256 del verifier, codificado en base64url (sin padding)
  • Un parámetro state: un valor aleatorio impredecible utilizado para prevenir CSRF

Conserva el codeVerifier y el state junto con la sesión del usuario (o, para herramientas CLI, en memoria del proceso) hasta que llegue el callback. Necesitarás ambos.

Indica en scope el acceso que necesita tu aplicación, separado por espacios (consulta Scopes). Los ejemplos solicitan los tres scopes porque más adelante esta guía emite una clave de API; una aplicación que solo lee solicita scope=read.

import base64
import hashlib
import secrets
from urllib.parse import urlencode

def new_pkce_pair():
    verifier = secrets.token_urlsafe(64)  # 86 chars after base64
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
    return verifier, challenge

client_id    = "PJjjlcKAQPCzIcaeprzEVg"
redirect_uri = "https://acme.example/oauth/callback"

code_verifier, code_challenge = new_pkce_pair()
state = secrets.token_urlsafe(32)

# Save code_verifier + state in the user's session before redirecting.

params = {
    "client_id":             client_id,
    "redirect_uri":          redirect_uri,
    "response_type":         "code",
    "scope":                 "read write physical",
    "state":                 state,
    "code_challenge":        code_challenge,
    "code_challenge_method": "S256",
}
authorize_url = f"https://console.rhombussystems.com/oauth/authorize?{urlencode(params)}"
print(authorize_url)
Advertencia

El endpoint de autorización usa los nombres de parámetros estándar de OAuth 2.0: client_id, redirect_uri, response_type=code, scope, state, code_challenge y code_challenge_method=S256. Solo se admite S256 como método de challenge. (Por compatibilidad, la consola también acepta las formas abreviadas redirect y challenge, pero prefiere los nombres estándar mostrados arriba.)

Redirige el navegador del usuario a la URL que construiste. El usuario iniciará sesión en Rhombus y aprobará tu aplicación.

Paso 3: Maneja el callback de redirección

Después de que el usuario se autentica, Rhombus redirige a tu redirectUri con parámetros de consulta:

En caso de éxito, el callback incluye:

ParámetroDescripción
codeCódigo de autorización para intercambiar por un access token. De corta duración.
stateEl valor de state que enviaste — debes verificar que coincida.
iss(cuando está configurado) El identificador del emisor (RFC 9207). Si está presente, valida que coincida con el servidor de autorización esperado.

En caso de fallo, el callback incluye error (p. ej., access_denied) y error_description (detalle legible para humanos) en lugar de code.

Advertencia

Verifica siempre que el parámetro state coincida con lo que enviaste. Una falta de coincidencia indica un posible ataque CSRF: aborta el flujo.

python
from flask import Flask, request, session, abort

app = Flask(__name__)

@app.route("/oauth/callback")
def callback():
    if request.args.get("error"):
        return f"Login failed: {request.args['error_description']}", 400

    code  = request.args.get("code")
    state = request.args.get("state")

    if state != session.pop("oauth_state", None):
        abort(400, "state mismatch")

    code_verifier = session.pop("oauth_verifier", None)
    # Proceed to Step 4 — exchange `code` for an access token.
    ...

Paso 4: Intercambia el código por un access token

Llama a POST https://auth-web.rhombussystems.com/oauth/token con el código de autorización y tu verifier PKCE. Esta es una solicitud de token OAuth 2.0 estándar: envía los parámetros codificados en formulario (application/x-www-form-urlencoded), no como JSON, y autentica tu cliente con su clientId/clientSecret, ya sea mediante HTTP Basic auth (client_secret_basic) o en el cuerpo de la solicitud (client_secret_post, mostrado abajo). Un cliente público (registrado con token_endpoint_auth_method none) no tiene secreto y envía solo client_id. Este es un host distinto del de la API principal: el endpoint /oauth/token reside en auth-web.rhombussystems.com.

import requests

token_response = requests.post(
    "https://auth-web.rhombussystems.com/oauth/token",
    headers={"Content-Type": "application/x-www-form-urlencoded"},
    data={
        "grant_type":    "authorization_code",
        "code":          code,
        "redirect_uri":  redirect_uri,
        "code_verifier": code_verifier,
        "client_id":     client_id,
        "client_secret": client_secret,
    },
)
token_response.raise_for_status()
tokens = token_response.json()

access_token  = tokens["access_token"]
refresh_token = tokens["refresh_token"]
expires_in_s  = tokens["expires_in"]
granted_scope = tokens["scope"]

Ejemplo de respuesta:

json
{
  "access_token": "AAAAAAAAAAAAAAAAAAAAAA",
  "refresh_token": "BBBBBBBBBBBBBBBBBBBBBB",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write physical"
}

scope lista los scopes que tiene el token, separados por espacios.

Paso 5: Llama a la API de Rhombus

Envía el access token en el encabezado estándar en cada llamada a la API de Rhombus:

  • Authorization: Bearer <accessToken>

Un token Bearer sin encabezado x-auth-scheme se interpreta como un access token OAuth, y Authorization: Bearer no transporta ningún otro tipo de credencial. El mismo encabezado funciona en la API REST (api2), en los hosts de medios y en el handshake del WebSocket de eventos. También se acepta el par de encabezados equivalente x-auth-scheme: api-oauth-token y x-auth-access-token: <accessToken>.

import requests

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api2.rhombussystems.com/api/user/getUsersInOrg",
    headers=headers,
    json={},
)

users = response.json().get("users", [])
for user in users:
    print(user.get("email"), "—", user.get("name"))
Éxito

Si esta llamada devuelve una lista de usuarios, tu flujo OAuth funciona de extremo a extremo. El usuario se autenticó, tienes un access token y estás llamando a la API en su nombre.

Scopes

Un scope limita lo que puede hacer un access token. Solicita los scopes en el parámetro scope de la URL de autorización, separados por espacios (por ejemplo, scope=read%20write). Los valores que Rhombus no reconoce se ignoran, y una solicitud sin scope recibe los tres. La pantalla de consentimiento lista los scopes que pide tu aplicación:

ScopePantalla de consentimientoQué cubre
readRead: ver cámaras, grabaciones y configuraciónOperaciones cuyo nombre empieza por get, find, search, list, export, count, query, lookup, fetch, download, view, describe o preview; medios y video en vivo; el WebSocket de eventos
writeWrite: cambiar configuración y datosTodas las demás operaciones
physicalPhysical: desbloquear puertas y ascensores, confinamiento, alarmas, altavoces y robotsDesbloqueos de puertas y ascensores (incluidos los de integraciones), confinamiento (lockdown), anulaciones de estado, relés, first-in, armar y desarmar, escalar a alarma, reproducción en altavoces, el socket en vivo del robot y el envío de audio de intercomunicación (talk)

La página de cada operación en la referencia de la API muestra el scope que necesita.

  • Los scopes nunca añaden acceso. Un token solo puede hacer lo que permiten a la vez el rol del usuario y sus scopes (consulta Scopes de OAuth).
  • El WebSocket de eventos necesita read. Un token sin ese scope se rechaza en el handshake.
  • El audio de intercomunicación se comprueba en cada mensaje. Enviar audio sin physical cierra el socket con el código 1008.
  • Crear, rotar y renovar claves de API requiere los tres scopes (consulta Emite una API key de larga duración).
  • Las respuestas de tokens incluyen scope, los scopes que tiene el token. El documento de descubrimiento del servidor de autorización (https://auth-web.rhombussystems.com/.well-known/oauth-authorization-server) y el documento de recurso protegido de la API (https://api2.rhombussystems.com/.well-known/oauth-protected-resource) listan los scopes admitidos en scopes_supported.

Una llamada que necesita un scope que el token no tiene devuelve 403 con errorCode PERMISSION_REQUIRED y un encabezado que indica el scope necesario:

http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="physical"

Para obtener el scope que falta, vuelve a llevar al usuario por el flujo de autorización con ese scope añadido a scope.

Tiempo de vida del access token

El campo expires_in en la respuesta de tokens te indica cuánto tiempo (en segundos) es válido el access token (típicamente una hora). Cuando expira, la API de Rhombus devuelve 401 con errorCode CREDENTIAL_EXPIRED; refréscalo.

Un cliente que guarda el refresh token mantiene el acceso mientras siga refrescando; la CLI de Rhombus funciona así. Un servicio en segundo plano, un daemon o un trabajo programado que no deba depender del inicio de sesión de un usuario puede, en cambio, emitir una clave de API con el access token (consulta Emite una API key de larga duración).

Refresh tokens

Intercambia el refresh token por un nuevo access token en el mismo endpoint /oauth/token, codificado como formulario:

cURLbash
curl -X POST "https://auth-web.rhombussystems.com/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN" \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET"   # solo clientes confidenciales
  • Los clientes públicos (registrados con token_endpoint_auth_method none, protegidos solo por PKCE) envían solo client_id. Los clientes confidenciales envían también client_secret, en el cuerpo o con HTTP Basic auth.
  • Cada refresh rota el refresh token. La respuesta incluye un nuevo refresh_token; guárdalo, porque el que enviaste deja de funcionar.
  • Nunca reutilices un refresh token reemplazado. Dentro de los 30 segundos posteriores a su reemplazo se rechaza. Pasados 30 segundos, presentarlo revoca toda la autorización, incluidas las claves de API emitidas con ella, y el usuario tiene que volver a iniciar sesión. Si varios procesos comparten una misma sesión, deja que solo uno de ellos refresque.
  • Un refresh token sin usar durante 90 días expira.
  • Un refresh fallido devuelve 400 con el error de OAuth invalid_grant: el refresh token se reemplazó, expiró o se revocó. Vuelve a llevar al usuario por el flujo de inicio de sesión.

Los access tokens y los refresh tokens son cadenas opacas, no JWT.

Revocar una autorización

Para terminar una autorización, envía el refresh token a https://auth-web.rhombussystems.com/oauth/token/revoke, codificado como formulario, con token, client_id y, en un cliente confidencial, client_secret. Revocar una autorización:

  • Detiene sus tokens en un plazo de 60 segundos en todos los hosts. Las conexiones abiertas al WebSocket de eventos reciben un frame STOMP ERROR y se cierran con el código 1008; los sockets de medios abiertos (video en vivo, intercomunicación, robot, PTZ) se cierran con el código 1008.
  • Elimina las claves de API emitidas con esa autorización.
  • Termina los tokens federados emitidos con esa autorización.

Eliminar la aplicación revoca todas sus autorizaciones. Consulta también Permisos y rotación de claves.

Emite una API key de larga duración

Una vez que un usuario ha iniciado sesión con OAuth, puedes intercambiar el access token de corta duración por una clave de API de larga duración, para un servicio que siga llamando a la API sin la sesión del usuario.

Llama a POST /api/integrations/org/submitApiTokenApplication con Authorization: Bearer <accessToken>. El token necesita los tres scopes (read, write y physical). El cuerpo indica el rol de la nueva clave en permissionGroupUuid: el rol del propio usuario, o uno que tenga permitido asignar. Lista los roles con getPermissionGroups y los roles asignables del usuario con getPermissionsForCurrentUser. Para que la clave caduque, añade expiresAtMs (milisegundos desde epoch). Algunos rechazos, como un rol que el usuario no puede asignar, devuelven HTTP 200 con "error": true y un errorMsg, así que revisa el cuerpo además del código de estado.

import requests

response = requests.post(
    "https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    },
    json={
        "displayName": "Acme Console — background worker",
        "permissionGroupUuid": role_uuid,
        "authType": "API_TOKEN",
    },
)

data = response.json()
if data.get("error"):
    raise RuntimeError(data.get("errorMsg"))
api_key = data["apiKey"]  # store this securely — treat it like a password

A partir de ese punto, usa la clave de API con los encabezados estándar x-auth-scheme: api-token + x-auth-apikey: no se requieren más llamadas OAuth. Para una clave de certificado (mTLS), envía "authType": "CERT" con un csr cuya firma sea válida; consulta Claves de API y certificados. El mismo access token puede rotar una clave (POST /api/integrations/org/rotateApiToken) o renovar el certificado de una clave de certificado (POST /api/integrations/org/renewApiTokenCert), con los mismos tres scopes; consulta Gestionar claves con la API.

Advertencia

Trata las claves de API emitidas como contraseñas. Actúan con el rol que indicaste en permissionGroupUuid y funcionan hasta que las eliminas o hasta que pasa su expiresAtMs. Revocar la autorización OAuth con la que se emitieron las elimina. Almacénalas cifradas en reposo, nunca en el control de versiones, y rota o elimina las claves no utilizadas.

Trabajar con cuentas partner

Los usuarios de una organización partner (MSP o revendedor) pueden completar el mismo flujo de "Iniciar sesión con Rhombus", y su access token va en el mismo encabezado Authorization: Bearer <accessToken>. (x-auth-scheme: partner-api-oauth-token con x-auth-access-token se acepta como alias.)

Para actuar dentro de una organización cliente gestionada, agrega x-auth-org: <clientOrgUuid>, exactamente igual que con una clave de API de partner. Es solo un encabezado, y un valor vacío cuenta como ausente. Un token de partner que llama a un endpoint a nivel de cliente sin x-auth-org actúa en la organización partner, y la respuesta incluye los encabezados Deprecation y Sunset: consulta Llamadas a la API Partner.

El callback no indica si el usuario es partner. Si llamas a POST /api/integrations/org/submitApiTokenApplication con el token de un usuario partner, la respuesta es 403 Forbidden. Vuelve a intentarlo con el endpoint de partner que se muestra abajo. La CLI de Rhombus detecta las cuentas partner de la misma forma al iniciar sesión, y rhombus login --partner omite esa comprobación.

Emite una clave de API de partner

Llama a POST /api/partner/submitApiTokenApplication con el access token, sin x-auth-org. El cuerpo es el mismo que el del endpoint de organización. El token necesita los tres scopes, y el rol de partner del usuario debe incluir la administración de la API.

cURLbash
curl -X POST "https://api2.rhombussystems.com/api/partner/submitApiTokenApplication" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Acme MSP integration", "permissionGroupUuid": "PARTNER_ROLE_UUID", "authType": "API_TOKEN"}'

La respuesta contiene apiKey. Úsala con x-auth-scheme: api-token y x-auth-apikey. Para actuar dentro de una organización cliente gestionada, agrega x-auth-org: <clientOrgUuid>. Consulta Llamadas a la API Partner, que también explica cómo rotar y renovar claves de partner.

Implementación de referencia

El Rhombus CLI es una referencia de producción para todo lo de esta guía. rhombus login inicia sesión con OAuth y mantiene vigentes los tokens del perfil en lugar de crear una clave de API. cmd/login.go recorre todo el flujo de extremo a extremo: registro dinámico como cliente público, generación de PKCE, servidor de callback local, construcción de la URL de autorización, intercambio de tokens y almacenamiento de los tokens; la CLI los refresca antes de que expiren.

Si algo en tu implementación no funciona, compara tu comportamiento contra el CLI: es el ejemplo canónico.

Solución de problemas

state no coincide en el callback

Tu callback recibió un valor de state distinto al que enviaste. Verifica que estás conservando el state que generaste en el Paso 2 junto con la sesión del usuario (o en memoria para herramientas CLI) y comparándolo en el callback. Una falta de coincidencia persistente puede indicar un intento de CSRF: aborta el flujo en lugar de reintentar silenciosamente.

El intercambio de tokens devuelve HTTP 400 o una respuesta con `error`

Causas comunes:

  • redirect_uri no coincide: el redirect_uri en el cuerpo del intercambio de tokens debe coincidir exactamente (incluyendo esquema, host, puerto y ruta) con el redirect_uri que enviaste en el Paso 2 y la URI registrada con tu aplicación OAuth.
  • code expirado o reutilizado: los códigos de autorización expiran a los 5 minutos y solo funcionan una vez. Intercámbialos en el callback.
  • code_verifier no genera el hash de code_challenge: verifica que estás usando SHA-256 y codificación base64url sin padding = tanto en la generación del challenge como en la transmisión del verifier.
  • Tipo de contenido o autenticación de cliente incorrectos: el endpoint de tokens espera application/x-www-form-urlencoded (no JSON). Un cliente confidencial envía su clientId/clientSecret mediante HTTP Basic auth o en el cuerpo del formulario (client_secret_post); un cliente público envía solo client_id. La solicitud de token no usa un encabezado x-auth-scheme.
El refresh devuelve 400 invalid_grant

El refresh token fue reemplazado por un refresh anterior, pasó 90 días sin usarse o su autorización se revocó: mediante el endpoint de revocación, al eliminar la aplicación o porque se presentó un refresh token reemplazado más de 30 segundos después de su reemplazo. Guarda siempre el refresh_token más reciente de cada respuesta. Cuando un refresh falla, vuelve a llevar al usuario por el flujo de inicio de sesión.

Las llamadas a la API fallan con 401 o 403 y un access token

Envía el token como Authorization: Bearer <token> (o con x-auth-scheme: api-oauth-token y x-auth-access-token), nunca en x-auth-apikey. Un 401 significa que el token falta, está mal formado, es desconocido, expiró o fue revocado; errorCode indica cuál (UNAUTHENTICATED, INVALID_CREDENTIAL, CREDENTIAL_EXPIRED, CREDENTIAL_REVOKED). Refresca un token expirado. Un 403 con PERMISSION_REQUIRED significa que el token es válido, pero el rol del usuario o los scopes del token no permiten la llamada; cuando falta un scope, el encabezado WWW-Authenticate lo indica (consulta Scopes). Consulta Cómo interpretar los errores de autenticación.

La redirección termina en la página de inicio de sesión de la consola otra vez sin código

El client_id que estás enviando puede no ser reconocido. Verifica que estás usando el clientId devuelto por submitApplication, no el UUID de aplicación de una respuesta diferente. Si rotaste aplicaciones, el clientId antiguo ya no es válido.

Próximos pasos