Skip to content

feat: consume embedded-handoff ZeroID token from URL hash (skip sign-in when framed) - #210

Merged
saucam merged 1 commit into
mainfrom
feat/embed-token-login
Jul 20, 2026
Merged

feat: consume embedded-handoff ZeroID token from URL hash (skip sign-in when framed)#210
saucam merged 1 commit into
mainfrom
feat/embed-token-login

Conversation

@saucam

@saucam saucam commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Problem

When codeoid's web UI is embedded by a host app (Highflame Studio's Agent Sandbox), the host has already authenticated the user and hands off a short-lived ZeroID token in the iframe URL hash — …/ui/#codeoid_token=<jwt>. But the web UI ignored it and rendered codeoid's own sign-in page inside the frame, so the "SSO" experience dead-ended at a login screen.

Fix

main.tsx now calls a new consumeEmbedToken() before the app boots:

  • Runs only when actually framed (window.parent !== window). At the top level ("Open in new tab") it's a no-op, so a token is never consumed outside the embed path.
  • Reads #codeoid_token from the hash, stores it in the OAuth-token slot (codeoid.token) via rememberOAuthToken() — so resolveToken() picks it up through its existing OAuth-token fallback and reloads stay signed in.
  • Scrubs the token from the address bar / history (replaceState), preserving any other hash state.

Security: the token is a normal ZeroID JWT the daemon still verifies on every WebSocket (JWKS signature, mandatory account_id/project_id, per-message expiry). This only skips the interactive sign-in; it does not bypass authorization.

Companion changes (Highflame side)

  • Studio already passes #codeoid_token when framing (highflame-studio).
  • Forge injects the ZeroID URL so the daemon's JWKS verification resolves.

Follow-ups (hardening, not required for this to work)

  • Daemon: enforce audience=codeoid and set frame-ancestors / CODEOID_EMBED_ALLOWED_ORIGINS to restrict which hosts may embed (clickjacking) — framing is currently open.

Test

  • web: vitest run src/lib/auth.test.ts25 passed (5 new: stores+scrubs when framed, resolveToken picks it up, preserves other hash state, no-op at top level, no-op without a token)
  • tsc -b --noEmit ✅ · eslint src

…p sign-in when framed)

When codeoid's web UI is embedded by a host app (Highflame Studio), the host has
already authenticated the user and hands off a short-lived ZeroID token in the
iframe URL hash (#codeoid_token=…). Until now the web UI ignored it and showed
codeoid's own sign-in page inside the frame.

main.tsx now calls consumeEmbedToken() before the app boots: when actually framed
(window.parent !== window) it reads #codeoid_token, stores it in the OAuth-token
slot (so resolveToken uses it and reloads stay signed in), and scrubs the token
from the address bar / history. At the top level ("Open in new tab") it's a no-op,
so a token is never consumed outside the embed path. The daemon still verifies the
token on every WebSocket (JWKS signature, tenancy, expiry), so this skips only the
interactive sign-in — it does not bypass authorization.

Tests: consumeEmbedToken stores + scrubs when framed, resolveToken then picks it
up, other hash state is preserved, and it's a no-op at top level / with no token.
web: vitest run src/lib/auth.test.ts (25) ✅ · tsc -b --noEmit ✅ · eslint ✅

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.02%. Comparing base (e0f2f9e) to head (3e351b4).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #210   +/-   ##
=======================================
  Coverage   88.02%   88.02%           
=======================================
  Files         129      129           
  Lines       22045    22045           
=======================================
  Hits        19405    19405           
  Misses       2640     2640           
Flag Coverage Δ
daemon 88.02% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@saucam
saucam merged commit 1caadab into main Jul 20, 2026
4 of 5 checks passed
@saucam saucam mentioned this pull request Jul 20, 2026
saucam added a commit that referenced this pull request Jul 20, 2026
Bump package.json 0.3.1 → 0.3.2 and roll the 0.3.2 CHANGELOG section
(the release workflow gates on package.json matching the tag). 0.3.2 carries
the SDLC pipeline primitive (#204#209), the /settings MCP Servers surface
(#203), embedded-handoff ZeroID token consumption (#210), and the cross-cutting
audit fixes (#211).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant