5 · Session Token (iframe)

Custom UI sayfan iframe içinde açıldığında, hangi tenant'ın baktığını App Bridge'den aldığın session token ile öğrenirsin. Token'ı backend'inde webhookSecret ile doğrularsın; böylece iframe'den gelen istekler güvenle tenant'a bağlanır.

Akış

iframe sayfan (frontend)
   │  const { data } = await bridgeCall('getSessionToken');   // App Bridge (postMessage)
   │  // data = { token, tokenType: "Bearer", expiresIn: 120 }   — TTL 120 sn, önceden alıp bekletme
   │  fetch('/api/me', { headers: { Authorization: 'Bearer ' + data.token } })
   ▼
Senin backend'in
   1) token'ı webhookSecret ile doğrula (JWT HS256) — audience = pluginId ZORUNLU
   2) claims: { iss, aud, sub, role, tenantId, pluginId, iat, exp }
        tenant = claims.tenantId · userId = claims.sub · role = claims.role (manager|staff)
   3) o tenant + kullanıcı için veriyi güvenle döndür

bridgeCall = App Bridge postMessage köprüsü (cevap { success, data } zarfında) — tam helper iframe App Bridge'de. Frontend yalnız token'ı taşır; doğrulama backend'de.

Doğrulama — resmi SDK (önerilen)

@restomenum/plugin-sdk verifySessionToken tüm kontrolleri (HS256, aud=pluginId, iss=restomenum, HMAC timing-safe, exp zorunlu, iat ileri-tarih reddi) tek çağrıda yapar — imza/kripto kendin yazma:

import { verifySessionToken, SessionError } from '@restomenum/plugin-sdk';

// GET /api/me — iframe'in Bearer token'ını doğrula:
try {
  const claims = await verifySessionToken(req.headers.authorization, {
    pluginId,                                                  // aud bununla eşleşmeli
    getSecret: (tenantId) => installStore.find(tenantId)?.webhookSecret,
  });
  // claims.tenantId · claims.sub (userId) · claims.role ('manager' | 'staff')
} catch (e) {
  if (e instanceof SessionError) res.status(401).json({ error: e.reason });
}
Çalışan tam örnek (canlı doğrulanmış): SDK örnek eklentisinin /ui (App Bridge) + /api/me (verifySessionToken) uçları.

Referans — manuel doğrulama (SDK altında ne yapar)

// iframe Custom UI — App Bridge session token doğrulama (Node)
// Sayfan Restomenum içinde iframe olarak açılır; tenant kimliğini App Bridge'den alırsın:
//   const { data } = await bridgeCall('getSessionToken');  // App Bridge → { token, tokenType:"Bearer", expiresIn:120 }
//   fetch('/api/me', { headers: { Authorization: 'Bearer ' + data.token } });
// Backend'inde token'ı webhookSecret ile doğrula (JWT HS256) — veya resmi SDK: verifySessionToken (önerilen):
import jwt from 'jsonwebtoken';

function verifySessionToken(bearer, webhookSecret, pluginId) {
  const token = bearer.replace(/^Bearer /, '');
  // HS256, webhookSecret ile imzalı. Claim'ler: { iss, aud, sub, role, tenantId, pluginId, iat, exp }
  //   sub  = userId (iframe'i açan kullanıcı)   role = manager | staff
  // audience (= pluginId) doğrulaması ZORUNLU — başka eklentinin token'ı kabul edilmesin:
  const claims = jwt.verify(token, webhookSecret, { algorithms: ['HS256'], audience: pluginId });
  // per-user yetki: claims.role / claims.sub ile kur (ad/PII için users/get — users:read + consent)
  return { tenantId: claims.tenantId, userId: claims.sub, role: claims.role, exp: claims.exp };
}

Önemli noktalar

  • Token kısa ömürlüdür; her isteği backend'de doğrula, iframe'e güvenme.
  • İmza anahtarı tenant'ın webhookSecret'ıdır (token exchange'ten).
  • audience = pluginId doğrulaması ZORUNLU (jwt.verify(…, { audience: pluginId }') — başka eklentinin token'ı kabul edilmesin. exp'in geçmediğini de doğrula.
  • sub = userId (iframe'i açan kullanıcı), role = manager | staff → per-user yetkiyi bununla kur (ad/PII için users/get, users:read + consent).
Erişim modeli: Plugin UI/aksiyon kullanımı (/plugins/ui, bridge, action, hook) tüm tenant kullanıcılarına açıktır (yalnız eklenti yönetimi manager-only). Belirli işlemleri kısıtlamak senin işin — gövdedeki/token'daki actor.role ile uygula (örn. "iadeyi yalnız manager yapabilir").
Session token, iframe'i çerçeveleyen panelden gelir. Panel origin'leri (DEV panel dahil): https://app.restomenum.com + https://test-restomenu.web.app — postMessage yalnız bu origin'lerle yapılmalı. Sayfan mutlaka iframe güvenliği kurallarına uymalı (CSP frame-ancestors + origin-pinli postMessage).