Merged
Size
M
Change Breakdown
Feature70%
Config15%
Testing15%
#28303feat(core): Wire up embed login end-to-end with cookie overrides and audit events (no-changelog)

Embed login wired end-to-end for iframe SSO

Embed login wired end-to-end for iframe SSO

The OAuth 2.0 Token Exchange initiative hits a major milestone: iframe-based single sign-on now works end-to-end with cross-origin cookie support, audit logging, and an independent feature flag for safe rollout.

The embed login flow was wired to real implementations, completing Phase 2a of the OAuth 2.0 Token Exchange initiative. Previously a skeleton, the endpoints now handle full authentication with cross-origin cookie overrides, audit event emission, and a feature gate for independent control.

Embedding n8n in iframes requires cookies that work across origins—a technical constraint that blocked this use case until now. By passing SameSite=None; Secure to the session cookie when login originates from an embed context, authenticated sessions now work inside cross-origin iframes. When users authenticate through an embedded n8n instance, an embed-login event fires with subject, issuer, and client IP for audit trail visibility. A new N8N_EMBED_LOGIN_ENABLED environment variable lets operators enable embed endpoints independently from the broader token exchange feature, allowing gradual rollout without full commitment.

The changes live in the @n8n/cli package's auth and token exchange modules.

View Original GitHub Description

Summary

This PR completes the embed login flow by wiring real implementations into the skeleton endpoints. It is the final integration step for Phase 2a of the OAuth 2.0 Token Exchange initiative — after this, iframe SSO works end-to-end.

What changed:

  • Cookie overrides for iframe contextAuthService.issueCookie() now accepts an optional cookieOverrides parameter. The embed controller passes SameSite=None; Secure so the session cookie works inside cross-origin iframes (previously a TODO).
  • Audit event emission — Successful embed logins emit an embed-login event with subject, issuer, and clientIp for audit trail visibility.
  • Structured return from embedLogin()TokenExchangeService.embedLogin() now returns { user, subject, issuer } instead of just the user, so the controller has the claims it needs for audit logging without re-parsing the token.
  • Feature gate — Added N8N_EMBED_LOGIN_ENABLED config flag so the embed endpoints can be toggled independently of the token exchange feature. Returns 501 when disabled.
  • Integration tests — Full integration test suite covering: valid GET/POST login with cookie assertions, expired/replayed/bad-signature/long-lived token rejection, JIT user provisioning, and returning-user profile sync.

How to test

  1. Set env vars:
N8N_ENV_FEAT_TOKEN_EXCHANGE=true
N8N_EMBED_LOGIN_ENABLED=true
  1. Configure a trusted key via N8N_TOKEN_EXCHANGE_TRUSTED_KEYS with an RSA public key
  2. Sign a JWT with the corresponding private key containing sub, email, iss, aud, iat, exp, jti claims
  3. GET /rest/auth/embed?token=<jwt> — should set an HttpOnly; Secure; SameSite=None cookie and redirect to /
  4. Verify expired tokens, replayed JTIs, and invalid signatures all return 401
  5. First login with a new sub should JIT-provision the user; second login should sync profile fields

See https://n8nio.slack.com/archives/C0ACFN1G7NK/p1775812851354889 for test scripts to help set this up.

Related

closes https://linear.app/n8n/issue/IAM-468

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions) <!-- **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** -->
  • Docs updated or follow-up ticket created.
  • Tests included. <!-- A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. -->
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)
© 2026 · via Gitpulse