Saltar al contenido
GuestMakerDesarrolladores
Vista generalDocumentaciónEventosSDKSandboxNovedades
EN/ES
Solicitar acceso
Vista generalDocumentaciónEventosSDKSandboxNovedades
Solicitar acceso
Volver al SSO de fidelización

Inicio rápido para motores de reservas

OIDC · independiente del framework

Guía para copiar y pegar que añade «Iniciar sesión en Recompensas» a un motor de reservas con el flujo Authorization Code + PKCE. Los fragmentos usan TypeScript de servidor (Node), fetch y Web Crypto; adáptalos a cualquier tecnología. Es la versión práctica de la referencia de SSO de fidelización.

Antes de empezar
Te integras como Relying Party (RP) confidencial.
  • Te proporcionamos un client_id y un client_secret (se muestra una vez) y registramos los valores exactos de redirect_uri que vayas a usar.
  • El emisor es https://www.guestmaker.ai/api/oidc. Lee siempre los endpoints y las JWKS desde el descubrimiento, en lugar de fijarlos en el código: rotamos las claves.
  • El intercambio de tokens y la verificación de id_token se ejecutan solo en tu backend. El client_secret nunca llega al navegador.
// Load discovery once at startup and cache it.
const ISSUER = "https://www.guestmaker.ai/api/oidc";
const disco = await fetch(`${ISSUER}/.well-known/openid-configuration`).then((r) => r.json());
// disco.authorization_endpoint, disco.token_endpoint, disco.jwks_uri, ...
Paso 1
Genera PKCE + state + nonce
Paso 2
Redirige al huésped a la autorización
Paso 3
Intercambia el código por tokens
Paso 4
Verifica el id_token
Paso 5
Consulta datos actuales del socio

1 · Genera PKCE, state y nonce

Crea un nuevo code_verifier por intento y su code_challenge S256. Genera valores aleatorios de state (CSRF) y nonce (protección frente a reutilización). Guarda los tres en la sesión del huésped usando como clave state.

import { randomBytes, createHash } from "node:crypto";

const base64url = (b: Buffer) =>
  b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

const codeVerifier = base64url(randomBytes(32));
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
const state = base64url(randomBytes(16));
const nonce = base64url(randomBytes(16));

// session.save({ state, nonce, codeVerifier })  // server-side, keyed by state

2 · Redirige al endpoint de autorización

Solicita solo los permisos que necesitas. openid es obligatorio; añade profile email para la identidad y loyalty:read transactions:read para los datos del socio.

const authUrl = new URL(disco.authorization_endpoint);
authUrl.search = new URLSearchParams({
  response_type: "code",
  client_id: CLIENT_ID,
  redirect_uri: REDIRECT_URI,            // must byte-match a registered value
  scope: "openid profile email loyalty:read transactions:read",
  state,
  nonce,
  code_challenge: codeChallenge,
  code_challenge_method: "S256",
}).toString();

// res.redirect(authUrl.toString())  // send the guest to our branded login

3 · Gestiona el callback e intercambia el código

El huésped vuelve a tu redirect_uri con code, state y iss. Confirma que state coincide con la sesión y que iss coincide con nuestro emisor; después intercambia el código de un solo uso (unos 60 s) con client_secret_basic.

// In your /callback handler: q = request query
if (q.state !== session.state) throw new Error("state mismatch");
if (q.iss && q.iss !== ISSUER) throw new Error("issuer mismatch");

const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");
const tokens = await fetch(disco.token_endpoint, {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    Authorization: `Basic ${basic}`,
  },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: q.code,
    redirect_uri: REDIRECT_URI,
    code_verifier: session.codeVerifier,
  }),
}).then((r) => r.json());
// tokens.access_token (RS256 JWT, ~5 min), tokens.id_token

4 · Verifica el id_token

Nunca confíes en un token sin verificar. Comprueba la firma frente a nuestras JWKS vinculadas a RS256, y comprueba iss, aud = your client_id, nonce y la caducidad. Usa cualquier biblioteca JOSE compatible; aquí, jose.

import { jwtVerify, createRemoteJWKSet } from "jose";

const jwks = createRemoteJWKSet(new URL(disco.jwks_uri)); // caches + refreshes on new kid

const { payload: claims } = await jwtVerify(tokens.id_token, jwks, {
  algorithms: ["RS256"],
  issuer: ISSUER,
  audience: CLIENT_ID,
});
if (claims.nonce !== session.nonce) throw new Error("nonce mismatch");

// Identity + tier/points ride in the claims (per granted scope):
//   claims.sub, claims.name, claims.email,
//   claims.loyalty_tier_name, claims.points_balance, claims.member_number
// session.login({ memberId: claims.sub, accessToken: tokens.access_token })

5 · Consulta datos actuales del socio

El id_token es una instantánea al iniciar sesión. Para obtener el saldo y el historial actuales, llama a los endpoints de recursos del socio con el access_token como token Bearer. Son específicos del socio: el token ya identifica al huésped, por lo que nunca pasas un correo electrónico ni un ID de socio.

const auth = { Authorization: `Bearer ${tokens.access_token}` };

const balance = await fetch(`${origin}/api/loyalty/me/balance`, { headers: auth })
  .then((r) => r.json());
// { points_balance, tier, pending_balance, credit, member_number, ... }

const history = await fetch(`${origin}/api/loyalty/me/transactions?limit=20`, { headers: auth })
  .then((r) => r.json());
// { transactions: [...], total, limit, offset }

No hay endpoint UserInfo: el perfil, el correo y el nivel llegan como declaraciones de id_token; los datos actuales provienen de /api/loyalty/me/*. Los tokens de acceso duran unos 5 minutos. Repite el flujo al caducar: la sesión del huésped persiste entre los hoteles del grupo, por lo que suele bastar una redirección silenciosa.

No omitas estos requisitos
Los registramos y exigimos: las integraciones que los omiten fallan.
  • Un nuevo code_verifier por intento; PKCE solo con S256 (nunca plain).
  • Verifica state, nonce y el parámetro de respuesta iss.
  • Verifica la firma de id_token frente a las JWKS vinculadas a RS256; rechaza alg: none / HS256.
  • redirect_uri debe coincidir exactamente, con HTTPS y sin comodines ni variantes de barra final.
  • Mantén client_secret en el servidor; intercambia tokens solo desde tu backend.

Los detalles completos, el modelo de tokens y el comportamiento de revocación están en la referencia de SSO de fidelización.

GuestMaker

El CRM con IA omnicanal
para grupos hoteleros.

● de Hotelinking ↗
DESARROLLARInicio rápidoReferencia de la APIAutenticaciónWebhooksSandbox de API
EXPLORAREventos webSDK y bibliotecasFidelización y SSOCrédito monetarioNovedades
CONECTARSolicitar acceso a la APIPlataforma GuestMakerIntegraciones
© 2026 GuestMaker · Hotelinking, S.L.Palma de Mallorca, España
PrivacidadCondicionesAviso legal