IFrame Embed Token Usage

IFrame Embed Token Usage

IFrame Embed Token Usage

Embedded Assessment / Portal Tokens

Embed tokens let a partner's backend drop an authenticated ActiFi view (an assessment, dashboard, roadmap, etc.) into an <iframe> on their own site without an interactive login.
The flow is three hops:
    Authenticate (server-to-server) — the partner backend exchanges its API-user credentials for a JWT. This JWT is a bearer credential used for all subsequent server-to-server REST calls.
    Mint (server-to-server) — the partner backend calls a REST endpoint with its API-user JWT and gets back a short-lived, single-use token.
    Exchange (in the browser) — the partner loads a public URL carrying that token as the <iframe src>. ActiFi burns the token, creates a browser session, and redirects in-frame to the destination.
The token that travels through the browser is single-use and expires in 60 seconds.


Prerequisites

  • Feature must be enabled for the tenant. The enableEmbedTokens tenant flag is a kill switch; while it is off, both endpoints reject. Ask your ActiFi contact to enable it.
  • An API user + JWT for the mint call. Ask your ActiFi contact to provide you with credentials, then see Step 1 below to turn those credentials into a JWT.
  • The destination must be a named RelayState that exists (and is active) in the tenant's SSO RelayState table. Raw paths and URLs are not accepted — only curated named destinations. Ask your ActiFi contact which RelayState names are available for your tenant.


Step 1 — Get an API-user JWT

Before you can mint an embed token you need a JWT for your API user. This is a one-time (or refresh-when-expired) call from your backend, never the browser.
POST /api/v3/rest/auth/token
Content-Type: application/json

Request body

{
"client_id": "<api-user-username>",
"client_secret": "<api-user-password>"
}
client_id / client_secret are the username and password of the API-user account your ActiFi contact provisioned for you — there is no separate OAuth client concept here.

Success response — 200

{
"result": {
"accessToken": "<jwt>",
"expiresOn": "<ISO date/time string>"
}
}
  • accessToken — the JWT. Use it as Authorization: Bearer <accessToken> on subsequent server-to-server REST calls, including the mint call in Step 2.
  • expiresOn — the JWT is valid for 1 hour from issuance. Request a new one once it expires (or proactively before each batch of calls); there is no refresh-token flow.

Error responses

Status
When
401
Invalid client_id/client_secret, or the account is inactive. Repeated failures trigger a temporary lockout (10 attempts, then a 5-minute lockout).
Scope note: a JWT issued to an API-user account can only be used against REST endpoints (paths under /v{n}/rest/..., e.g. the embed-token endpoints below). It cannot be used to access the standard app UI routes.
Keep it server-side. The API-user JWT must never be exposed to the browser — only mint embed tokens with it from your backend.


Step 2 — Mint a token (server-to-server)

Call from your backend, never the browser — the API-user JWT must not be exposed client-side.
POST /api/v3/rest/auth/embed/token
Authorization: Bearer <API-USER-JWT>
Content-Type: application/json

Request body

{
"userRef": {
"type": "tenantSpecificUserId",
"value": "123abc"
}
}
Field
Type
Notes
userRef.type
string
"username" or "tenantSpecificUserId".
userRef.value
string
The identifier for the user the embedded session will act as.
tenantSpecificUserId is convenient when your system already stores its own user ID against the ActiFi user — you don't need to know ActiFi's username.

Success response — 200

{
"status": "success",
"result": {
"embedToken": "Yk9f... (opaque, ~43+ chars)",
"expiresIn": 60
}
}
  • embedToken — the single-use token. Use it immediately (see Step 2).
  • expiresIn — token lifetime in seconds (currently 60).

Error responses

Status
When
400
userRef is missing or malformed (bad type, missing value).
401
Missing / invalid JWT, or the caller is not an API user.
403
Embed tokens are not enabled for this tenant (kill switch off).
422
The referenced user cannot be issued a token (unknown, inactive, locked).
Note on 422: the message is intentionally generic and does not reveal whether the user exists — this prevents user enumeration.


Step 3 — Exchange the token (in the iframe)

Build the iframe src from the token you just minted plus the destination RelayState, and render it. Loading this URL is the exchange — there is no separate call.
GET /pub/auth/embed/exchange?token=<embedToken>&relayState=<relayStateName>
<iframe
src="https://<your-actifi-host>/pub/auth/embed/exchange?token=Yk9f...&relayState=recent_assessment"
width="100%"
height="800"
frameborder="0">
</iframe>

The relayState parameter

  • A named RelayState: relayState=assessment1001
  • Some destinations take an id, appended with a colon: relayState=open_roadmap:1234
  • Both token and relayState are required and must each appear exactly once.

What happens on success

The endpoint responds with a 302 redirect through ActiFi's auth wall (/auth/wall?jwt=...&redirect=...), which seeds the session and lands the iframe on the destination. Your code does not handle the JWT — it stays inside ActiFi's flow.
The resulting browser session lasts 2 hours.

Failures

Every failure renders the same neutral page ("This link is invalid") so nothing is revealed about the cause. A code query param is included on that page to help ActiFi support correlate the failure in the logs:
code
Meaning
404
Embed tokens are not enabled for this tenant (kill switch off).
400
Missing/duplicated token or relayState, or an unknown/invalid RelayState.
410
Token is unknown, expired, already used, or the user is no longer valid.
A 400 (bad/unknown RelayState or malformed params) does not consume the token — fix the request and retry with the same token if it is still within its 60-second window. A 410 means the token is spent or expired; mint a new one.


End-to-end example

// --- your backend ---

// Step 1: authenticate as the API user (cache/reuse until expiresOn, ~1hr)
const tokenRes = await fetch("https://<actifi-host>/api/v3/rest/auth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: API_USER_USERNAME,
client_secret: API_USER_PASSWORD,
}),
});
const { result: tokenResult } = await tokenRes.json();
const API_USER_JWT = tokenResult.accessToken;

// Step 2: mint a short-lived embed token
const mintRes = await fetch(
"https://<actifi-host>/api/v3/rest/auth/embed/token",
{
method: "POST",
headers: {
Authorization: `Bearer ${API_USER_JWT}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userRef: { type: "tenantSpecificUserId", value: partnerUserId },
}),
}
);
const { result } = await mintRes.json();
const embedToken = result.embedToken; // use within 60s

// hand the token to the browser (e.g. in the page render) and build the iframe src:
const relayState = "recent_assessment";
const src =
`https://<actifi-host>/pub/auth/embed/exchange` +
`?token=${encodeURIComponent(embedToken)}` +
`&relayState=${encodeURIComponent(relayState)}`;
<!-- your page -->
<iframe src="{{ src }}" width="100%" height="800" frameborder="0"></iframe>


Operational notes & best practices

  • Cache the API-user JWT, don't re-mint per request. It's valid for 1 hour — fetch once and reuse it for all embed-token mints (and other server-to-server REST calls) until it expires, then request a new one.
  • Mint an embed token per page-load. A token is single-use and 60-second-lived. Mint one right before you render the iframe; do not cache or reuse embed tokens.
  • Never mint or authenticate from the browser. Both the API-user JWT and the API-user's client_id/client_secret are privileged credentials and must stay server-side. Only the short-lived embed token ever reaches the client.
  • One token → one iframe. The first exchange burns the token. A second exchange of the same token fails and raises an internal reuse alert.
  • Framing is allow-listed. Your site's origin must be permitted to frame the ActiFi destination host. Confirm your domain is on the allow-list with your ActiFi contact.
  • Third-party cookie caveat. Some browser upload features rely on cookies that may be blocked inside a third-party iframe; the session itself does not depend on them, but file-upload-heavy flows may behave differently framed vs. standalone.