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.
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ürbridgeCall = 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.
@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 });
}/ui (App Bridge) + /api/me (verifySessionToken) uçları.// 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 };
}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)./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").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).