Seguridad

Cómo AllSign protege cada sesión de firma embebida — desde la generación del token hasta la entrega del PDF firmado.

Modelo de seguridad

Inspirado en Plaid Link moderno (post-2020) y Veriff (session tokens). Cada sesión tiene múltiples capas:

┌─ Tu backend (secret key: allsign_live_sk_xxx) ───────────┐
│  POST /v2/signing-sessions                                │
│  → Crea documento + firmantes + config                    │
│  → Devuelve client_secret (token efímero, 15 min)         │
│  → La secret key NUNCA sale de tu backend                 │
└───────────────────────────────────────────────────────────┘

          │  client_secret (single-use, scoped a 1 session)

┌─ Tu frontend (solo el client_secret) ────────────────────┐
│  JS SDK monta iFrame con el client_secret                 │
│  → client_secret es la UNICA credencial en el browser     │
│  → postMessage con origin validation                      │
│  → CSP frame-ancestors restringe dominios                 │
└───────────────────────────────────────────────────────────┘

          │  iFrame carga sign.allsign.io

┌─ AllSign ────────────────────────────────────────────────┐
│  Token hash (SHA-256) — nunca se almacena en texto plano  │
│  Sesión atada a: documento + firmante + timestamp         │
│  OTP opcional pre-firma (email o WhatsApp)                │
│  Camera permissions para IDV embebido                     │
└───────────────────────────────────────────────────────────┘

Secret key vs Client secret

AllSign usa una sola credencial en el frontend: el client_secret. No hay publishable key. Este es el mismo modelo que Plaid adoptó en 2020 cuando deprecó su publicKey:

CredencialPrefijoDónde se usaAlcanceVida
Secret keyallsign_live_sk_Solo tu backend (server-side)Crear documentos, signing sessions, leer datos, verificar firmasPermanente (hasta que la rotes)
Client secretas_sess_..._secret_...Tu frontend (browser)Montar UNA sola session de firma15 minutos, single-use
// ✅ Backend — usa la secret key
const response = await fetch('https://api.allsign.io/v2/signing-sessions', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer allsign_live_sk_xxx' },
  body: JSON.stringify({ document_id, signer_email })
});
const session = await response.json();
// session.client_secret → pásalo a tu frontend

// ✅ Frontend — usa SOLO el client_secret
const signing = AllSign.init({ clientSecret: session.client_secret });
signing.modal();

// ❌ NUNCA pongas la secret key en el frontend
// El SDK detecta este error y lo rechaza con un mensaje claro

Signing Sessions (client_secret)

El client_secret generado por POST /v2/signing-sessions tiene estas propiedades:

PropiedadValor
Tiempo de vida15 minutos desde la generación
UsoSingle-use — una vez que se abre, el token se consume
AlmacenamientoEl token se hashea con SHA-256 antes de guardarse en DB
ContenidoSession ID + firmante + documento + timestamp + nonce encriptado
RevocaciónSe invalida automáticamente si el documento cambia de estado
AlcanceSolo puede montar el widget — no puede leer ni modificar datos

Buenas prácticas

  • Crea la session justo antes de que el usuario necesite firmar — no la pre-generes
  • Nunca expongas el client_secret en logs — trátalo como credencial temporal
  • Si expira, crea una nueva session — el endpoint es idempotente para el mismo firmante
// ✅ Correcto: crea la session al momento de abrir
async function openSigning() {
  const { client_secret } = await fetch('/api/create-session').then(r => r.json());
  allsign.modal({ clientSecret: client_secret });
}

// ❌ Incorrecto: pre-crear la session al cargar la página
const session = await fetch('/api/create-session').then(r => r.json());
// ... 20 minutos después ...
allsign.modal({ clientSecret: session.client_secret }); // SESSION_EXPIRED

Content Security Policy (CSP)

Para que el iFrame de AllSign funcione en tu sitio, tu CSP debe permitir frames desde el dominio de AllSign.

Configuración requerida

Agrega estas directivas a tu Content Security Policy:

frame-src https://sign.allsign.io;

Si usas el CDN del SDK:

script-src https://js.allsign.io;
frame-src https://sign.allsign.io;

Ejemplo en meta tag

<meta http-equiv="Content-Security-Policy"
  content="frame-src https://sign.allsign.io; script-src 'self' https://js.allsign.io;">

Ejemplo en header HTTP (Next.js)

// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "frame-src https://sign.allsign.io; script-src 'self' https://js.allsign.io;"
  }
];

Frame-ancestors (del lado de AllSign)

AllSign configura frame-ancestors en las respuestas del iFrame para restringir qué dominios pueden embeber la firma. Cuando envías allowed_origins, AllSign genera automáticamente el header frame-ancestors con esos dominios. Si no envías allowed_origins, se usa frame-ancestors * (cualquier dominio puede embeber).


Restricción de origen (allowed_origins)

Cuando creas una signing session, puedes pasar allowed_origins para restringir desde qué dominios se puede usar el client_secret. Esto protege contra un escenario de phishing:

Sin allowed_origins:

  1. Tu backend crea session → client_secret viaja al browser
  2. Atacante intercepta el client_secret (XSS, proxy, devtools)
  3. Atacante monta el iframe de AllSign en sitio-falso.com
  4. Firmante ve iframe REAL de AllSign dentro de una página falsa
  5. Firma creyendo que está en tu sitio → phishing exitoso

Con allowed_origins: ["https://tu-app.com"]:

  1. Tu backend crea session con allowed_origins
  2. Atacante intercepta el client_secret
  3. Atacante monta el iframe en sitio-falso.com
  4. AllSign detecta que parent_origin es sitio-falso.com
  5. → 403 ORIGIN_NOT_ALLOWED — el iframe no carga

Uso

Pasa el campo allowed_origins al crear la session:

// Backend — al crear la signing session
const response = await fetch('https://api.allsign.io/v2/signing-sessions', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer allsign_live_sk_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    document_id: 'doc_xxx',
    signer_email: 'firmante@empresa.com',
    allowed_origins: ['https://tu-app.com', 'https://staging.tu-app.com'],
  }),
});
Comportamientoallowed_originsResultado
No enviado o []Cualquier dominio puede embeber (menos seguro)
["https://tu-app.com"]Un dominioSolo ese dominio puede usar el client_secret
["https://tu-app.com", "https://staging.tu-app.com"]Varios dominiosCualquiera de la lista es aceptado
Cualquier valorhttp://localhost:*Siempre permitido — no necesitas agregarlo para desarrollo local

¿A quién protege?

A tu integración y a tus firmantes. Si un atacante obtiene un client_secret (por ejemplo, via XSS en tu sitio), no puede reutilizarlo en otro dominio para hacer phishing. El firmante solo puede interactuar con AllSign desde los dominios que tu backend autorizó.


Validación de origen (postMessage)

El SDK usa postMessage para comunicación entre tu página y el iFrame de AllSign. Internamente, el SDK valida que los mensajes provengan del origen correcto:

// Esto lo hace el SDK automáticamente — no necesitas implementarlo
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://sign.allsign.io') return;
  // Procesar evento...
});

Verificación de identidad pre-firma

Para documentos sensibles, puedes requerir verificación de identidad antes de que el firmante acceda al documento. Esto se configura al crear el documento:

{
  "name": "Contrato de arrendamiento",
  "document": { "base64Content": "..." },
  "participants": [{
    "name": "Juan Pérez",
    "email": "juan@empresa.com"
  }],
  "signatureValidation": {
    "autografa": true,
    "document_scan": true,
    "selfie_capture": true,
    "biometric_signature": true
  }
}

Cuando el widget se monta, el firmante primero pasa por el pipeline de verificación (escaneo de INE, selfie, match biométrico) y solo después puede firmar.

Flujo con verificación

Widget se monta
  → Escaneo de INE (OCR + validación)
  → Selfie del firmante
  → Face match biométrico (INE vs selfie)
  → ✅ Identidad verificada
  → Mostrar documento para firma
  → Firma (autógrafa o simple)
  → ✅ Documento firmado con evidencia de identidad

Todos estos pasos ocurren dentro del widget embebido — el firmante nunca sale de tu aplicación.


OTP pre-firma (opcional)

Agrega una capa adicional de verificación con OTP (One-Time Password) por email o WhatsApp:

{
  "participants": [{
    "name": "Juan Pérez",
    "email": "juan@empresa.com",
    "whatsapp": "+521234567890"
  }]
}

La configuración OTP se pasa al crear la Signing Session, no al crear el documento:

{
  "document_id": "DOC_UUID",
  "signer_email": "juan@empresa.com",
  "otp_mode": "required"
}

El firmante recibe un código de 6 dígitos que debe ingresar antes de poder firmar. El código expira en 10 minutos y tiene máximo 5 intentos.


Checklist de seguridad

Antes de ir a producción con firma embebida, verifica:

  • Tu secret key (allsign_live_sk_*) está en variables de entorno del backend — nunca en el frontend, nunca commiteada al repo
  • Las Signing Sessions se crean server-side con tu secret key — el frontend solo recibe el client_secret
  • Envías allowed_origins con los dominios de producción al crear la session (ver sección)
  • El client_secret se crea justo antes de abrir el widget, no al cargar la página (TTL de 15 min)
  • El client_secret no se escribe a logs ni se guarda en storage cliente (localStorage, cookies, etc.)
  • Tu CSP permite frame-src https://sign.allsign.io
  • Si embedes IDV (escaneo de INE), tu iFrame tiene allow="camera"
  • Tienes webhooks configurados como fuente de verdad (no solo callbacks del SDK)
  • Si usas HMAC en webhooks, verificas la firma en cada request
  • Las acciones críticas (pagos, activaciones) dependen del webhook o return URL, no del callback del SDK
  • Para documentos sensibles, tienes verificación de identidad habilitada
  • En producción, test_mode está desactivado

Siguiente paso

  • Quickstart — Implementa tu primera firma embebida
  • Webhooks — Configura y verifica webhooks con HMAC

Was this page helpful?