Ir al contenido

Guía Rápida

Esta guía resume los pasos para integrar PASS Beneficios en tu plataforma. Para detalles de cada paso, consulta las secciones correspondientes.

  1. Genera tu par de llaves RSA

    Crea una llave privada (3072 bits):

    openssl genrsa -out private.pem 3072

    Extrae la llave pública:

    openssl rsa -in private.pem -pubout -out public.pem

    Envía public.pem a Präna. Nunca compartas private.pem.

    Detalle completo

  2. Recibe tus credenciales

    Präna te entregará:

    • hostId — identificador de tu organización
    • clientId — identificador de tu aplicación
    • secretId — secreto para firmar tokens JWT (se usa como CM_JWT_SECRET en tu .env)

    Detalle completo

  3. Configura las variables de entorno

    Crea un archivo .env en la raíz de tu proyecto con las credenciales que te entregó Präna:

    CM_HOST_ID=tu-host-id
    CM_CLIENT_ID=tu-client-id
    CM_JWT_SECRET=tu-secret-id
    CM_API_URL=https://tu-url-dev-proporcionada.run.app

    Y coloca tu llave privada en keys/private.pem.

  4. Crea el servidor de autenticación

    Instala las dependencias:

    npm init -y && npm install express cors jsonwebtoken dotenv

    Este servidor genera el JWT, firma el request con RSA-SHA256 y obtiene el userToken de PASS Beneficios. Crea un archivo server.js:

    import express from "express";
    import cors from "cors";
    import jwt from "jsonwebtoken";
    import { createSign } from "crypto";
    import { readFileSync } from "fs";
    import { join, dirname } from "path";
    import { fileURLToPath } from "url";
    import "dotenv/config";
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    
    const app = express();
    app.use(cors({ origin: "*" }));
    app.use(express.json());
    
    // Función para firmar la solicitud con RSA-SHA256
    async function signRequest(clientId, method, url, body, hostId, token, privateKey) {
      const timestamp = Date.now().toString();
    
      const headersToSign = {
        "content-type": "application/json",
        "x-cm-host-id": hostId,
        "x-cm-client-id": clientId,
        authorization: `Bearer ${token}`,
      };
    
      const headerLines = Object.entries(headersToSign)
        .map(([k, v]) => `${k}:${v}`)
        .join("\n");
    
      const bodyStr = body ? JSON.stringify(body) : "";
      const payload = `${method}\n${url}\n${timestamp}\n${bodyStr}\n${headerLines}`;
    
      const signer = createSign("RSA-SHA256");
      signer.update(payload);
      signer.end();
    
      const signature = signer.sign(privateKey).toString("base64");
    
      return { signature, timestamp };
    }
    
    // Endpoint para autorizar usuario
    app.post("/api/authorize", async (req, res) => {
      try {
        const { email, firstName, lastName } = req.body;
    
        if (!email || !firstName || !lastName) {
          return res.status(400).json({
            error: "Faltan datos requeridos: email, firstName, lastName",
          });
        }
    
        const hostId = process.env.CM_HOST_ID;
        const clientId = process.env.CM_CLIENT_ID;
        const jwtSecret = process.env.CM_JWT_SECRET;
        const apiUrl = process.env.CM_API_URL;
    
        const privateKeyPath = join(__dirname, "keys", "private.pem");
        const privateKey = readFileSync(privateKeyPath, "utf8");
    
        const jwtPayload = {
          sub: hostId,
          email: email,
          name: firstName,
          paternalLastName: lastName.split(" ")[0],
          ...(lastName.split(" ")[1] && {
            maternalLastName: lastName.split(" ")[1],
          }),
          iss: clientId,
        };
    
        const token = jwt.sign(jwtPayload, jwtSecret, {
          algorithm: "HS256",
          expiresIn: "5m",
          header: { kid: clientId, alg: "HS256" },
        });
    
        const endpoint = `/hosts/${hostId}/authorize-user`;
    
        const { signature, timestamp } = await signRequest(
          clientId, "GET", endpoint, null, hostId, token, privateKey
        );
    
        const response = await fetch(`${apiUrl}${endpoint}`, {
          method: "GET",
          headers: {
            Authorization: `Bearer ${token}`,
            "X-CM-Timestamp": timestamp,
            "X-CM-Signature": signature,
            "X-CM-HOST-ID": hostId,
            "X-CM-Client-ID": clientId,
            "Content-Type": "application/json",
          },
        });
    
        if (!response.ok) {
          const errorData = await response.json();
          return res.status(response.status).json({
            error: errorData.message || "Error al autenticar",
          });
        }
    
        const data = await response.json();
        return res.json({ userToken: data.userToken });
      } catch (error) {
        return res.status(500).json({
          error: "Error interno del servidor",
          details: error.message,
        });
      }
    });
    
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Servidor corriendo en http://localhost:${PORT}`);
    });

    Detalle de autenticación · Detalle de firma RSA

  5. Levanta el servidor y obtén el userToken

    Inicia el servidor:

    node server.js

    Prueba la autenticación:

    curl -X POST http://localhost:3000/api/authorize \
      -H "Content-Type: application/json" \
      -d '{"email":"[email protected]","firstName":"Juan","lastName":"Pérez López"}'

    Respuesta:

    {
      "userToken": "eyJhbGciOiJIUzI1NiIs..."
    }

    El userToken tiene una validez de 1 hora.

    Detalle completo

  6. Integra en tu plataforma

    Elige el método que mejor se adapte:

    Monta un iframe con la URL del marketplace:

    <iframe
      src="https://hola.passbeneficios.com?hostId={HOST_ID}&userToken={USER_TOKEN}&origin={TU_ORIGIN}"
      style="width: 100%; height: 100vh; border: none;"
      allow="clipboard-read; clipboard-write;"
    />

    Documentación completa del embebido

RequisitoDetalle
Llave privada RSA3072 bits, formato PEM
CredencialeshostId, clientId, secretId
JWTHS256, expiración 5 minutos
FirmaRSA-SHA256 sobre el request
userTokenValidez 1 hora, se obtiene del API