Beágyazott oldalak
Az app saját felülete a Dynex bal menüjéből nyílik, iframe-ben. Az oldalad egy aláírt kontextus-tokenből tudja, ki nézi, és egy postMessage hídon beszél a kerettel.
Az app minden embeds[] bejegyzése egy menüpont a bérlő bal menüjében. Rákattintva a Dynex az /app/apps/<slug>/… oldalon egy teljes magasságú iframe-ben tölti be az app_url + path címet, a query-ben egy aláírt kontextus-tokennel:
1https://app.example.com/dashboard2 ?dynex_token=<base64url(payload)>.<base64url(HMAC-SHA256(embed_secret, base64url(payload)))>3 &dynex_theme=light|dark # az induló téma, hogy az első festés se villanjonKét token, egy formátum. A query-ben (dynex_token) érkező token PII-mentes: csak az azonosítókat hordozza, a tenantName, userEmail és userRole mezője üres string - az iframe URL az app hozzáférési naplójába és a Refererbe kerül, oda ezek nem valók. A teljes kontextus tokenje a hídon (dynex:context, a dynex:ready után azonnal, majd 4 percenként) és a POST /api/apps/embed/verify válaszában jön - a nevet, e-mailt, szerepet onnan vedd (bridge.onContext).
| Payload-mező | Jelentés |
|---|---|
installId | A telepítés azonosítója (= a token-csere install_id-ja). |
tenantId | A bérlő azonosítója. |
tenantName | A bérlő neve. A query-tokenben üres (a hídon és a verify-válaszban kitöltve). |
userId | A Dynex-felhasználó azonosítója, aki az oldalt nézi. |
userEmail | A felhasználó e-mail címe. A query-tokenben üres (csak a hídon). |
userRole | A felhasználó szerepe a bérlőnél (pl. owner, admin, staff). A query-tokenben üres (csak a hídon). |
locale | Nyelv (jelenleg hu). |
theme | light | dark - kövesd, hogy az iframe ne üssön el a kerettől. |
appSlug | Az app slugja. |
iat | Kiadás (unix mp). |
exp | Lejárat (unix mp): a kiadástól 5 perc. Ellenőrzéskor 30 mp óraeltérés megengedett. |
nonce | Egyszeri véletlen - visszajátszás-védelemhez az app oldalán. |
Ellenőrzés: helyben a verifyEmbedTokenForInstall(token, getEmbedSecret)-tel (a token telepítésének tárolt embed_secret-jével), vagy a Dynexen a POST /api/apps/embed/verify végponton ({ client_id, client_secret, token } → { ok, context, exp, nonce }), ami azt is megnézi, hogy a telepítés még aktív-e. A helyi ellenőrzés mellé ajánlott a rendszeres távoli ellenőrzés (pl. minden új sessionnél), mert az eltávolítást vagy felfüggesztést csak a Dynex látja. A token rövid életű: a betöltés utáni saját sessionödet te kezeled, ne a tokenre építs.
- A token a query-ben érkezik: PII-mentes, de azonosítókat hordoz (
tenantId,userId,installId) és bemutatásra jogosít: ne naplózd (a hozzáférési naplóból maszkold adynex_token-t), ellenőrzés után azonnal válts saját sessionre, és 302-vel irányíts a token nélküli URL-re, hogy ne maradjon az előzményekben és a Refererben. - A kontextus mezői (
tenantName,userEmail, …) bérlői bevitel: HTML-be csak escape-elve írd ki, különben tárolt XSS az app originjén. - Az egyszeri
nonceellenőrzése nem elvárás - a védelem az aláírás és a 5 perces lejárat. Ha mégis egyszer fogadod el, tartsd a nonce-t csak a lejáratig, és az élő sessiont ne dobd el: a Dynex keret újratöltése ugyanazzal a tokennel jöhet. - Iframe-ben a saját sütid csak
SameSite=None; Secure-ral megy; Safari a harmadik-fél sütit alapból blokkolja - ott a Storage Access API vagy a hídon 4 percenként érkező friss token a megoldás.
Mindkét irány kizárólag a másik fél originjével megy (targetOrigin és event.origin ellenőrzés), és az app csak a közvetlen szülő ablak (event.source === window.parent) üzenetét fogadja el. Az üzenetek JSON-objektumok, a type mező a diszkriminátor.
| Irány | Üzenet | Mezők | Hatás |
|---|---|---|---|
| app → Dynex | dynex:ready | - | Az app betöltött; a Dynex válaszul dynex:context-et küld. |
| app → Dynex | dynex:resize | height | Az iframe magasságának beállítása (px). |
| app → Dynex | dynex:navigate | path | Navigálás a Dynexen belül (pl. /app/reservations); válasz: dynex:navigate-result. |
| app → Dynex | dynex:toast | kind, message | Értesítés a keretben (success | error | info | warning). |
| app → Dynex | dynex:getContext | - | Kontextus újrakérése; válasz: dynex:context. |
| app → Dynex | dynex:openExternal | url | Külső URL megnyitása új lapon. |
| Dynex → app | dynex:context | context, token | A kontextus (a fenti payload mezői) és egy friss token. |
| Dynex → app | dynex:theme | theme | Téma-váltás (light | dark). |
| Dynex → app | dynex:navigate-result | ok, path, error? | A navigálás eredménye. |
1import { createAppBridge } from "@dynex/app-sdk/browser";2 3// A Dynex origin a SZERVER konfigjából (pl. <meta name="dynex-origin">), ne a referrerből:4// egy idegen oldalba ágyazott másolatnál a referrer a támadó originje lenne.5const dynexOrigin = document.querySelector('meta[name="dynex-origin"]')?.content;6const bridge = createAppBridge({ dynexOrigin }); // kihagyva: a dynex_origin query-paraméter, majd a referrer - csak https://*.dynex.hu számít, különben prod.dynex.hu7bridge.onContext(({ context }) => render(context)); // context.tenantName, userEmail: escape-elve jelenítsd meg8bridge.onTheme((theme) => document.documentElement.dataset.theme = theme);9bridge.autoResize(); // ResizeObserver → dynex:resize10bridge.ready();11saveButton.onclick = () => bridge.toast("success", "Elmentve.");12link.onclick = () => bridge.navigate("/app/reservations");A Dynex az app-oldalakon frame-src 'self' https: CSP-fejlécet küld (fejlesztői buildben http://localhost:* is engedett), a valódi kapu az alkalmazás-réteg: az iframe csak az aktív, jóváhagyott (vagy saját sandbox-) telepítés app_url-jét tölti be, és a keret minden üzenet előtt ellenőrzi, hogy az iframe originje az appé. Élesben ezért a sandbox-telepítéshez is https app_url kell (pl. ngrok / cloudflared alagút). Felfüggesztett app menüpontja eltűnik, és az iframe nem töltődik be.
A másik irány a te dolgod
A beágyazott oldalad küldjön Content-Security-Policy: frame-ancestors https://prod.dynex.hu fejlécet, hogy idegen oldal ne ágyazhassa be.
A fejlesztői portálon az app „Beágyazás-próba” fülén a beágyazott oldalad a teszt-fiókodban töltődik be, ugyanazzal a kerettel, eredet-ellenőrzéssel és üzenet-szabályokkal, mint élesben. A navigációt és a külső linkeket a próba nem hajtja végre, csak naplózza.
- Mi kell hozzá: kész teszt-fiók, az app telepítve a teszt-fiókra (a kontextus-token a telepítés titkával van aláírva), és megadott
app_url. - Oldal, al-útvonal, újratöltés: új keret töltődik be, új tokennel, ahogy élesben egy menüpontra kattintva.
- Téma, nyelv, szerepkör: a keret marad, az appod új
dynex:contextüzenetet kap (témaváltásnáldynex:themeis megy) - így az élő frissítést próbálhatod ki. - Keret szélessége: 390 px, 768 px vagy teljes szélesség, a telefonos és a táblagépes elrendezés ellenőrzéséhez.
- Üzenetnapló: minden üzenet a keret és a Dynex között, a legfrissebb felül. A tokenek maszkolva szerepelnek, a napló csak a böngésződ memóriájában él, és JSON-ként kimásolható.
Amíg az app nincs beküldve, az app_url lehet http://localhost cím is; beküldéshez nyilvános https cím kell. Hogy a helyi cím hol töltődik be, az a kerettől és a böngészőtől függ:
| Hol | http://localhost | Mit tegyél |
|---|---|---|
| Beágyazás-próba, Chrome és Firefox | Betölt. | Nincs teendő. |
| Beágyazás-próba, Safari | Nem tölt be: a Safari a https oldalba ágyazott http címet vegyes tartalomként letiltja. | Használj HTTPS-alagutat (pl. cloudflared, ngrok), és azt add meg app_url-nek. |
A Dynex alkalmazás kerete (/app/apps/…), élesben | Nem tölt be: a keret csak https címet enged. | HTTPS-alagút, a teszt-telepítéshez is. |
- Alagút mögött a beágyazott oldalad https-en fut, ezért a saját sütidnek
SameSite=None; Securekell, ahogy élesben. - A híd originjét ilyenkor is a szervered konfigjából add meg (
dynexOrigin), ne a referrerből. - Az alagút indításának lépései a Webhookok és események oldalon vannak; ugyanaz az alagút szolgálja ki a webhookot és a beágyazott oldalt.