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 allsign.io/embed/sign/{session_id}

┌─ 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://allsign.io;

Si usas el CDN del SDK:

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

Ejemplo en meta tag

<meta http-equiv="Content-Security-Policy"
  content="frame-src https://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://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, a partir del allowed_origins de la sesión. Como allowed_origins es obligatorio en producción (ver abajo), tu iframe live siempre queda restringido a tus dominios autorizados.


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'],
  }),
});
Entorno de la keyallowed_originsResultado
live (producción)No enviado o []400 ALLOWED_ORIGINS_REQUIRED — la sesión no se crea
live (producción)["https://tu-app.com"]Solo ese dominio puede usar el client_secret
live (producción)varios dominiosCualquiera de la lista es aceptado
test / dev (sandbox)No enviado o []Permitido (DX de sandbox) — cualquier dominio puede embeber
Cualquier entornohttp://localhost:*Siempre permitido — no necesitas agregarlo para desarrollo local

Error: ALLOWED_ORIGINS_REQUIRED

Si tu backend crea una sesión live sin allowed_origins, la API responde:

{
  "error": {
    "code": "ALLOWED_ORIGINS_REQUIRED",
    "type": "bad_request",
    "message": "Casi listo: agrega 'allowed_origins' con el dominio donde montas el iframe...",
    "field": "allowed_origins",
    "example": { "allowed_origins": ["https://app.tuempresa.com"] },
    "documentation_url": "https://developers.allsign.io/embedded/security"
  }
}

La solución es agregar el arreglo allowed_origins con el dominio de tu app (no el de AllSign):

POST
/v2/signing-sessions
curl -X POST "https://api.allsign.io/v2/signing-sessions" \
  -H "Authorization: Bearer allsign_live_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "document_id": "DOC_UUID",
    "signer_email": "firmante@empresa.com",
    "allowed_origins": ["https://app.tuempresa.com"]
  }'

¿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://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",
  "documents": [{ "base64Content": "...", "fileType": "pdf" }],
  "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 (no disponible aún — fase 2)

Cuando esté disponible, la configuración OTP se pasará al crear la Signing Session (no al crear el documento). El único valor funcional hoy es:

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

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 — obligatorio para keys live (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://allsign.io
  • Si embedes IDV (escaneo de INE), tu Permissions-Policy / CSP no bloquea la cámara — el SDK ya aplica allow="camera; microphone; clipboard-write" al iframe automáticamente; solo evita bloquearlo (este item solo aplica si construyes tu propio iframe sin el SDK)
  • 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
  • ErroresORIGIN_NOT_ALLOWED, CSP_BLOCKED y demás códigos, con su recuperación

Was this page helpful?