You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "madhushreeag (via GitHub)" <gi...@apache.org> on 2026/09/16 21:50:50 UTC

[I] [SIP] Proposal for one-time login tokens for iframe embedding [superset]

madhushreeag opened a new issue, #44363:
URL: https://github.com/apache/superset/issues/44363

   ## [SIP] Proposal for one-time login tokens for iframe embedding
   
   ### Motivation
   
   Organizations increasingly want to embed Superset dashboards and charts as iframes inside
   their own web applications, presenting them to a user who is *already signed in* to that
   application. Today this is difficult to do as the real, fully-permissioned user when
   Superset sits behind an SSO provider that uses a browser-redirect login flow
   (OAuth2 / OIDC / SAML), because the redirect flow does not run inside an iframe:
   
   1. **The IdP refuses to be framed.** Many identity providers send
      `X-Frame-Options: DENY` or a restrictive `Content-Security-Policy: frame-ancestors`,
      so the login redirect cannot render inside the iframe.
   2. **Third-party cookies are blocked.** Even when the IdP can be framed, the redirect
      chain relies on cookies that are now treated as third-party in an embedded context.
      Modern browsers block these by default, so the session cookie is never established.
   
   So even though the embedding application already holds a trustworthy proof of the user's
   identity, there is currently no clean way to turn that proof into a normal Superset session
   inside the frame. The common workaround is a "click to open in a new window" affordance,
   which abandons the embedded experience entirely.
   
   This is not hypothetical. At least one large deployment behind an enterprise OIDC provider
   independently produced a design document describing precisely this mechanism — a token
   exchange yielding a short-lived, single-use handle passed to the iframe — because there was
   no upstream option. Absent a supported path, deployments in this position either fork the
   security API or give up on embedding as the real user.
   
   Superset already provides an embedding path for a related but distinct need — **guest
   tokens + the Embedded SDK** — which lets a host grant scoped access to specific dashboards,
   with host-supplied row-level security, without the viewer needing a Superset account. That
   fits cases where the host is the source of authorization and the embedded viewer does not
   need a real Superset identity.
   
   This proposal targets a different case: the embedding application already has the user's
   identity, and requires the embedded view to be **a 1-to-1 mirror of that user's own Superset
   session** — the same account, the same roles, the same row-level security, the same object
   ownership and audit trail they would get by navigating to Superset directly. Many
   organizations need the same RBAC to apply whether Superset is accessed directly or embedded;
   constructing a separate guest session with host-supplied RLS does not meet that requirement,
   because the authorization then lives in the host rather than in Superset.
   
   ### Proposed Change
   
   Introduce an opt-in, two-step **one-time login token** flow that lets a trusted parent
   application establish a real, fully-authenticated Superset session inside an iframe without
   running the interactive SSO redirect in the frame.
   
   Both steps live on one path on the existing security REST API, distinguished by verb:
   
   ```
   POST /api/v1/security/login-token/     mint
   GET  /api/v1/security/login-token/     consume
   ```
   
   **Step 1 — mint a token (server-to-server).**
   
   The endpoint resolves the caller's identity through a **pluggable identity resolver** (see
   below), and on success:
   
   - generates a cryptographically random, opaque token (a `uuid4`),
   - stores the resolved `userinfo` in Superset's existing key-value store via `KeyValueDAO`
     under a new `KeyValueResource`, with a short server-side TTL (`expires_on`),
   - returns `{ "access_token": "<opaque-token>", "expires_at": <unix-ts> }`.
   
   No identity data is placed in the token itself; the token is only an opaque handle to a
   short-lived server-side record.
   
   **Step 2 — consume the token in the iframe.**
   
   The browser reaches this by navigating to the iframe `src`, so it must be a `GET`, and it
   returns a 302 + `Set-Cookie` rather than JSON. The endpoint:
   
   - looks up the `userinfo` by token **under a row lock**
     (`KeyValueDAO.get_entry(..., for_update=True)`),
   - deletes the entry in the same transaction, making single use atomic rather than
     best-effort — two concurrent requests cannot both observe it,
   - provisions/syncs the real user and their roles via `security_manager.auth_user_oauth()`,
     the same code path a normal OAuth login uses,
   - calls `login_user()` to establish the standard session cookie,
   - redirects to the validated `next` URL.
   
   From that point the iframe holds an ordinary Superset session: identical RBAC, RLS,
   ownership, saved objects and audit attribution to a normal login. Links, drill-throughs
   and navigation to any route work because a real session cookie is present.
   
   **Pluggable identity resolver.** The only deployment-specific part is *how the caller
   proves who they are* at mint time. This is exposed as a configurable hook so Superset core
   stays provider-agnostic. The resolver receives the incoming request and returns a
   `userinfo` dict, or `None`/raises to reject:
   
   ```python
   class LoginTokenUserInfo(TypedDict, total=False):
       username: str
       email: str
       first_name: str
       last_name: str
       role_keys: list[str]
   
   def LOGIN_TOKEN_IDENTITY_RESOLVER(
       request: Request, **kwargs: Any
   ) -> LoginTokenUserInfo | None:
       ...
   ```
   
   This is deliberately **not a new contract**: it is exactly the `userinfo` dict
   Flask-AppBuilder's `auth_user_oauth` already consumes. `username` identifies the user
   (falling back to `email`), and `role_keys` are resolved through `AUTH_ROLES_MAPPING`.
   Reusing it means provisioning and role sync are delegated wholesale to existing, exercised
   code rather than reimplemented.
   
   It also answers the central security question. Because `role_keys` resolve only against
   `AUTH_ROLES_MAPPING` — a mapping the *operator* controls — and `get_roles_from_keys` only
   returns roles that already exist, a parent application **cannot invent or escalate a role**.
   It can only present keys the operator has already authorized. Authorization stays with
   Superset, which is precisely the requirement that rules out guest tokens for this use case.
   
   Deployments can plug in, for example:
   
   - validating an OIDC / SSO **id token** presented by the parent app,
   - an already-authenticated Superset **session** (the caller mints a handoff token for its
     own identity),
   - **username/password** verified against the configured auth backend,
   - exchanging a **custom opaque token** with an internal identity service.
   
   **Worked example — OAuth2 / OIDC.** A common deployment has the embedding application and
   Superset both registered with the same IdP. The mint call is made **from the embedding
   service's backend**, so the primary credential never travels through the browser — only the
   opaque one-time token does:
   
   1. The embedding service's **backend** holds (or obtains via the OAuth2 Token Exchange
      grant, RFC 8693) an id token scoped to Superset's audience for the signed-in user.
   2. The backend calls `POST /api/v1/security/login-token/` with that id token. The resolver
      validates it (issuer, audience, signature, expiry) and returns the opaque
      `access_token`.
   3. The backend hands *only* the opaque token to its frontend, which sets it as the iframe
      `src`: `.../api/v1/security/login-token/?token=<opaque>&next=/dashboard/1/`.
   4. The browser loads the iframe, the token is consumed and deleted, and the frame now holds
      the user's real Superset session.
   
   **Flow diagram:**
   
   ```mermaid
   sequenceDiagram
       participant IdP as OAuth2 / OIDC IdP
       participant Backend as Embedding Service<br/>(backend)
       participant Mint as Superset<br/>POST login-token
       participant KV as Key-Value Store
       participant Frame as Browser (iframe)
       participant Consume as Superset<br/>GET login-token
       participant SM as Security Manager
   
       Backend->>IdP: token exchange -> id token (aud: superset)
       IdP-->>Backend: id token
       Backend->>Mint: POST login-token (id token, server-to-server)
       Mint->>Mint: identity resolver validates -> userinfo
       Mint->>KV: store userinfo, short TTL, key = random uuid
       Mint-->>Backend: { access_token, expires_at }
       Backend->>Frame: only the opaque token -> iframe src
       Frame->>Consume: GET login-token?token=...&next=...
       Consume->>KV: row-locked get + delete (atomic single use)
       Consume->>SM: provision/sync user + roles
       Consume-->>Frame: Set-Cookie (session) + 302 -> next
       Frame->>Frame: renders as the real, authenticated user
   ```
   
   **Security properties (built in):**
   
   - **Single use, atomically.** The entry is row-locked before it is read and deleted in the
     same transaction, so a race cannot double-spend it.
   - **Short TTL.** Server-side expiry, independent of the URL. Default 60 seconds.
   - **Opaque handle.** No identity or claims travel in the URL, referrer, history or logs.
   - **Uniform failure.** Unknown, malformed, expired and already-spent tokens all return an
     identical 401, so the response cannot be used to probe token state.
   - **A raising resolver rejects.** An exception during validation is treated as a denial, not
     a server error — a validation failure surfacing as a 500 would be indistinguishable from
     an outage, and a retrying caller must never be mistaken for an authenticated one.
   - **Validated redirect.** `next` is checked with the existing internal-URL helper and falls
     back to `/`, so it cannot become an open redirect.
   - **Closed by default.** Two independent switches are required — the feature flag *and* a
     configured resolver. With either missing, both endpoints return 404 rather than 403. A
     half-configured deployment of this feature would be worse than not having it.
   - Rate-limiting the mint endpoint is recommended.
   
   ### New or Changed Public Interfaces
   
   **New endpoints (one path, two verbs)**
   
   - `POST /api/v1/security/login-token/` — mint a one-time login token. A JSON endpoint on
     the security API, public at the API layer exactly like the existing
     `POST /api/v1/security/login`, and a direct sibling of
     `POST /api/v1/security/guest_token`; the identity resolver is what gates it. Response:
     `{ access_token, expires_at }`.
   - `GET /api/v1/security/login-token/` — consume the token and establish the session.
     Public (the opaque token is the credential), returning 302 + `Set-Cookie`. Query params:
     `token` (required), `next` (optional, validated).
   
   Both verbs share one path deliberately. An earlier draft split them across a JSON API route
   and a browser auth-view route on the theory that `/api/v1` should return JSON only, but that
   is not an existing constraint — `/api/v1` already serves `image/png` for chart and dashboard
   screenshots and `application/zip` for exports. One path means one place to reason about, one
   CSRF entry, and no risk of shadowing the dynamic `/login/<provider>` route.
   
   **New config / feature flag**
   
   - `LOGIN_TOKEN` feature flag, off by default.
   - `LOGIN_TOKEN_IDENTITY_RESOLVER` — callable resolving a request to `userinfo`; `None` by
     default, which leaves the feature inert.
   - `LOGIN_TOKEN_TTL_SECONDS` — server-side token lifetime, default 60.
   - One entry added to `WTF_CSRF_EXEMPT_LIST` for the mint endpoint. Minting is a
     server-to-server call from a backend that holds no Superset session and therefore has no
     CSRF token to present, and there is no session for an attacker to forge against — the same
     rationale as the token-authenticated chart-data and datasource-query endpoints already
     listed there. Without it the endpoint returns `400 The CSRF token is missing` and the flow
     cannot be used at all. Only the `POST` requires it; `GET` is not CSRF-protected.
   
   **New model / storage**
   
   - A new `KeyValueResource` enum value in `superset/key_value/types.py`. No new table — this
     reuses the existing `key_value` store, `KeyValueDAO` and `JsonKeyValueCodec`.
   
   **Interaction with existing auth settings**, which operators should be aware of:
   
   - `AUTH_USER_REGISTRATION` (default `False`) — an unknown username is rejected with 401
     rather than auto-created. With it on, unknown users are provisioned with
     `AUTH_USER_REGISTRATION_ROLE`.
   - `AUTH_ROLES_SYNC_AT_LOGIN` (default `False`) — with it off, an existing user's roles are
     never modified by this flow. With it on, the resolver's `role_keys` replace them on every
     consumption, bounded by `AUTH_ROLES_MAPPING`.
   
   **No changes** to existing visualizations, dashboards, React components, the CLI, or the
   default deployment. The Embedded SDK and guest-token flow are untouched; this is an
   additional, independent option.
   
   ### Required deployment configuration
   
   The flow establishes a normal session cookie, so the usual embedding constraints apply and
   are worth stating concretely rather than left to the reader:
   
   - **Framing.** Superset must permit being framed by the parent origin, via
     `frame-ancestors` in `TALISMAN_CONFIG` (or the equivalent in `HTTP_HEADERS`).
   - **Cookies, cross-site.** If the parent application is on a *different registrable domain*,
     `SESSION_COOKIE_SAMESITE = "None"` and `SESSION_COOKIE_SECURE = True` are required — the
     default `Lax` withholds the cookie in a cross-site frame, and the user lands on a login
     page even though the flow itself worked correctly. Browsers reject `SameSite=None` without
     `Secure`, so HTTPS is required.
   - **Cookies, same-site.** If the parent and Superset share a registrable domain (common in
     enterprise deployments, e.g. both under `example.com`), the defaults are sufficient and
     none of the above applies.
   - **Third-party cookie deprecation.** Even with `SameSite=None`, current browsers block
     third-party cookies for many users. Genuinely cross-domain embedding may additionally
     require the Storage Access API or a partitioned (CHIPS) cookie. This proposal does not
     change that, and does not claim to solve it.
   
   ### New dependencies
   
   None. The feature is built entirely on primitives already in Superset: the `key_value` store
   and its DAO/codecs, Flask-AppBuilder's security manager and user provisioning,
   `flask_login.login_user`, and the existing safe-redirect utility. No new `npm` or `PyPI`
   packages are required.
   
   ### Migration Plan and Compatibility
   
   - **No database migration.** The feature reuses the existing `key_value` table; the only
     schema-level addition is a new `KeyValueResource` enum value in code.
   - **Opt-in and backward compatible.** Gated behind a feature flag that is **off by default**,
     and inert unless an identity resolver is configured. Existing deployments are unaffected;
     no stored URLs change.
   - **Rollback** is disabling the flag. Any outstanding tokens self-expire via TTL.
   
   ### Known limitations
   
   Stated explicitly so reviewers can weigh them:
   
   - **The resolver is the entire authentication boundary.** Whatever it accepts is what
     Superset will issue a session for. A permissive resolver is a full impersonation
     vulnerability. This is the same trust placed in an OAuth provider's claims today, but it
     moves the validation into operator-supplied code, so the documentation must be emphatic.
   - **The token is a bearer credential for its lifetime.** Anyone who obtains it — from a
     reverse-proxy access log, for instance — can redeem it. Single use and a seconds-long TTL
     bound the exposure; they do not eliminate it. Operators terminating TLS in front of
     Superset may wish to redact the `token` query parameter from access logs.
   - **It does not solve third-party cookie blocking.** See required configuration above.
   
   ### Rejected Alternatives
   
   **1. Use Superset's built-in guest tokens + Embedded SDK.** Guest tokens are the closest
   existing feature: they grant scoped access to specific dashboards with host-supplied
   row-level security, without the viewer needing a Superset account. They address a different
   need:
   
   - *Host-defined session vs. the real user.* A guest token establishes a host-defined guest
     session (typically a shared `GUEST_ROLE_NAME` with host-supplied RLS). This works when the
     host is the source of authorization. Our case is the inverse: the user already exists in
     Superset with their own roles and RLS, and the requirement is that the same RBAC apply in
     the embed as when accessing Superset directly — so re-deriving an equivalent session in the
     host would move authorization out of Superset and duplicate what it already enforces.
   - *Scoped resources vs. full session.* Guest tokens scope access to a registered set of
     embedded resources via `/embedded/<uuid>`. This proposal targets the full application
     surface the user normally has (Explore, SQL Lab, drill-through, links between objects).
   - *Header-carried vs. cookie-carried.* Guest tokens are carried as a request header injected
     by the `superset-embedded-sdk`, suited to SDK-managed components. Full-page navigation
     inside the frame relies on a standard session cookie instead.
   - *Attribution.* A shared guest identity attributes activity to the guest rather than the
     individual; this proposal preserves per-user audit trails.
   
   Guest tokens build a host-defined session for the host; this proposal reflects an existing,
   fully-provisioned user session into the frame. The two are complementary, and this SIP
   leaves the guest-token flow untouched.
   
   **2. Run the SSO/OAuth redirect flow inside the iframe.** This is the root problem: IdPs
   commonly refuse framing (`X-Frame-Options` / `frame-ancestors`), and the redirect chain
   depends on cookies that browsers now block as third-party in embedded contexts. It does not
   work reliably across modern browsers.
   
   **3. Pass the raw provider token (e.g. the SSO id token) directly in the iframe URL.**
   This leaks a long-lived, broadly-scoped credential into browser history, `Referer` headers,
   and server access logs. The one-time, short-lived, opaque token minimizes blast radius and
   carries no identity data in the URL.
   
   **4. Rely on silent auth / shared cookies / `postMessage` token injection.** Fragile under
   third-party-cookie deprecation and cross-origin restrictions; requires the parent to inject
   scripts and the browser to permit third-party storage, which is increasingly unavailable.
   
   **5. Long-lived API JWT + a fully custom embedded frontend.** Superset's server-rendered
   pages (dashboards, SQL Lab) depend on the session cookie and CSRF protections; a bearer-only
   approach would require rebuilding those surfaces and still would not give the iframe a real
   browser session.
   
   **6. Split mint and consume across an API route and a browser view route.** Rejected: the
   premise that `/api/v1` must be JSON-only does not hold (screenshots return `image/png`,
   exports return `application/zip`), and splitting them doubles the surface to reason about
   for no benefit.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] [SIP-229] Proposal for one-time login tokens for iframe embedding [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas commented on issue #44363:
URL: https://github.com/apache/superset/issues/44363#issuecomment-5801815845

   Has this gone out to the dev@ mailing list as a [DISCUSS] thread yet? That's the usual next step before we can open it up for a vote.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] [SIP-229] Proposal for one-time login tokens for iframe embedding [superset]

Posted by "kadubhumika (via GitHub)" <gi...@apache.org>.
kadubhumika commented on issue #44363:
URL: https://github.com/apache/superset/issues/44363#issuecomment-5808918323

   Hi team! I am an intermediate backend engineer with strong experience in Python, SQLAlchemy, and PostgreSQL optimization (including connection pooling and indexing, logging). I would love to work on this issue and raise a PR to fix it. Could you please assign this to me?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [I] [SIP-229] Proposal for one-time login tokens for iframe embedding [superset]

Posted by "madhushreeag (via GitHub)" <gi...@apache.org>.
madhushreeag commented on issue #44363:
URL: https://github.com/apache/superset/issues/44363#issuecomment-5817373358

   > Has this gone out to the dev@ mailing list as a [DISCUSS] thread yet? That's the usual next step before we can open it up for a vote.
   
   Not yet, I'll put it up for a discuss today.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org