The snippet
What embed.js loads, what it reads, and what it sends.
<script async src="https://<site>.<our domain>/embed.js"></script>The script is built from the service's repository, served from the site's own address on the service, and runs on the publisher's Ghost site: the only origin from which Ghost's members endpoints answer with the visitor's session.
What it does, in order
GET https://<site>.<our domain>/api/embed/config(cached five minutes, answered withAccess-Control-Allow-Originfor the site's origin): the mapped tier ids, the button label, the landing page's address, and the site's members API address (<ghost url>/members/api, subdirectory aware).GET <members api>/memberwith the site's own cookie: who the visitor is. A visitor who is not signed in, or holds no mapped tier, gets no button; anydata-enova-contributeelement becomes a link to the landing page.- On click: a blank tab is opened at once (popup blockers allow that only inside the click), then
GET <members api>/sessionreturns the member's identity token (a JWT valid ten minutes), and a form in that tab poststokentop-level toPOST https://<site>.<our domain>/auth/member. The token never enters a URL. - The service verifies the token against the site's JWKS (
/members/.well-known/jwks.json, RS512, issuer and audience<site>/members/api, scopemembers:identity), accepts it once, maps the member through the Admin API, checks the tier, and answers a redirect to the dashboard with a cookie bound to that site.
What it never does
It sets no cookies on the site, reads no cookie, writes no HTML from data it received, and talks to nobody but the site and the service's config endpoint. It works with a content-security policy on the site that allows the script's origin; it needs no unsafe-inline.
Ghost(Pro)
Hosted sites may sit behind an edge challenge on the members endpoints; the script runs as a top-level page script so a challenge can be served. If the members endpoint cannot be read, the email sign-in on the landing page is the fallback.