Skip to content

feat(auth): Login with Thingtime anywhere — federated hints, SSO handoff, FedCM IdP - #327

Closed
lopugit wants to merge 2 commits into
claude/auto-login-passkey-support-593cd3from
claude/login-with-thingtime-anywhere
Closed

feat(auth): Login with Thingtime anywhere — federated hints, SSO handoff, FedCM IdP#327
lopugit wants to merge 2 commits into
claude/auto-login-passkey-support-593cd3from
claude/login-with-thingtime-anywhere

Conversation

@lopugit

@lopugit lopugit commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Stacked on #323 (passkeys + cross-deployment auto-login) — merge that first; this diff is the federation layers only. Full architecture note: PRs/login-with-thingtime-anywhere.md.

Owner's design constraint, honored end to end: quick-login is powered by the sessions in the user's browser, federated per deployment — never a central session store. thingtime.com acts only as a broker reading the browser's own tt_hints cookie and its own switcher roster; every deployment vouches exclusively for its own sessions.

🌐 Layer 1 — Federated hint resolution (multi-database environments)

/api/v1/auth/account-hints now reports foreign-database origins as unresolved; the client fans out (cap 4) to each origin's new /api/v1/auth/account-hints/resolve — CORS-credentialed for the Thingtime family only, resolves only pointers its own origin wrote, read-only (never prunes, never sets cookies). The browser assembles the full cross-environment picture.

🔁 Layer 2 — Cross-origin session handoff

For Thingtime deployments outside the cookie family (immutable *.vercel.app previews, custom domains):

  • POST /auth/sso-handoff (signed-in): mints a 2-minute, aud-bound, single-use code backed by a pre-minted session that self-expires unclaimed.
  • POST /auth/sso-session (receiving deployment): signature + aud === own public origin (origins stay default-open — per-code binding is the security, per the PR Thingtime SSO: default-open origins — any URL (incl. preview deploys) can use Login with Thingtime #87 decision), atomic single claim — replay revokes the session (theft signal) — then the exact password-login tail (cookies, roster merge, hints pointer). Different-environment redemption fails closed.
  • UI: /authorize?self=1 popup — embedded login with the hints strip when signed out, "Continue to ?" confirm card when signed in; foreign origins get a "Sign in with Thingtime 🌈" card in place of the hints popup.

✨ Layer 3 — FedCM identity provider

/.well-known/web-identity → config → accounts / client-metadata / assertion. The browser (never the page) fetches accounts with first-party cookies and draws its native "Continue as…" sheet on any domain; Sec-Fetch-Dest: webidentity is enforced so page JS can't read the endpoints. Accounts = this browser's own switcher roster (anti-fixation gate intact — only redeemable sessions are offered). Assertions mint handoff codes (thingtime-self) or baseline-profile app tokens (registered ttapp_… clients; wider grants still require the consent popup). The client feature-detects IdentityCredential and prefers the native sheet, popup as the everywhere-fallback.

🧪 Verification

  • verify-federated-login.mjs — 31/31 against two stacks on separate mongods — per-environment authority, CORS allow/deny, handoff aud-binding + cross-environment fail-closed + replay-revokes, and the complete FedCM accounts→assertion→session loop. The script header documents the recipe and its two sharp edges (stack B must be a production build — nitro's dev dotenv re-overrides .env on config reloads — against a second mongod, because the home db name is pinned to thingtime).
  • verify-passkeys.mjs still 44/44; lint clean; browser-verified both /authorize?self=1 states (screenshots in comments).
  • TESTING.md gained a checklist section; remix/CHANGELOG.md updated.

🤖 Generated with Claude Code

@gitguardian

gitguardian Bot commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
36358199 Triggered Generic Password b68fb94 remix/scripts/verify-federated-login.mjs View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thingtime Ready Ready Preview Aug 19, 2026 9:28am

Request Review

@github-actions github-actions Bot added the ai-rebase-in-progress AI stack rebase currently owns this PR branch label Aug 19, 2026
@lopugit
lopugit force-pushed the claude/auto-login-passkey-support-593cd3 branch from 3227349 to 18b6c56 Compare August 19, 2026 09:23
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Auto-rebase running — the stack rebase started working on this PR at 09:23 UTC.

Expected to finish around 09:38 UTC (most rebases still land in 5-15 minutes; the hosted-runner ceiling is 360). On success this branch is force-pushed onto its new base and a result comment follows — no manual action is needed meanwhile.

Live status

lopugit and others added 2 commits August 19, 2026 09:26
…off, FedCM IdP

Extends #323's auto-login beyond the *.thingtime.com cookie family while
honoring the owner's constraint end to end: quick-login is powered by the
sessions in the USER'S BROWSER, federated per deployment — never a central
session store. thingtime.com is only a broker reading the browser's own
tt_hints cookie and its own switcher roster.

Layer 1 — federated hint resolution (multi-database environments):
account-hints reports foreign-DB origins as 'unresolved'; the new
GET /api/v1/auth/account-hints/resolve (CORS-credentialed, Thingtime
family only, read-only) lets each environment vouch for exactly the
pointers its origin wrote; useAccountHints fans out (cap 4) and merges
by user id. The browser assembles the full picture.

Layer 2 — cross-origin session handoff: POST /auth/sso-handoff mints a
2-minute, aud-bound, single-use code (purpose-fenced JWT backed by a
pre-minted session that self-expires unclaimed); the receiving
deployment's POST /auth/sso-session verifies signature + aud === its own
public origin, claims atomically once (replay revokes — theft signal),
then runs the exact password-login tail. Different-environment
redemption fails closed. UI: /authorize?self=1 popup (embedded login
with hints strip when signed out, "Continue to <host>?" card when
signed in) and a "Sign in with Thingtime" card on foreign origins.

Layer 3 — FedCM IdP: /.well-known/web-identity + config/accounts/
client-metadata/assertion endpoints; Sec-Fetch-Dest: webidentity
enforced; accounts = this browser's own roster (anti-fixation gate
intact — only redeemable sessions are offered); assertion mints handoff
codes for thingtime-self or baseline-profile app tokens for registered
clients; the foreign-origin card prefers the native sheet via
IdentityCredential feature-detect.

Verified: scripts/verify-federated-login.mjs 31/31 against two stacks on
SEPARATE mongods (recipe + its two sharp edges documented in the script
header: stack B must be a production build — nitro dev dotenv
re-overrides .env on config reloads — and a second mongod, because the
home db name is pinned to 'thingtime'). verify-passkeys.mjs still 44/44.
Browser-verified both /authorize?self=1 states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the ai-rebase-in-progress AI stack rebase currently owns this PR branch label Aug 19, 2026
@lopugit
lopugit force-pushed the claude/login-with-thingtime-anywhere branch from 6909b76 to c232535 Compare August 19, 2026 09:28
@github-actions

Copy link
Copy Markdown
Contributor

🧬 Rebased claude/login-with-thingtime-anywhere onto claude/auto-login-passkey-support-593cd3 with the AI PR/stack rebase workflow.

  • Previous head: 6909b76432c7573330cabcedddf280f0601f8ba6
  • Rewritten head: c23253525f401edd93b3abe8fd6bbd9ab36b0bd7
  • graphify: refreshed and committed (semantic mode: none)
  • Direct stacked children dispatched: 0

The branch was published once with an exact force-with-lease. Please review the rewritten commits before relying on them.

@lopugit

lopugit commented Aug 19, 2026

Copy link
Copy Markdown
Owner Author

Collapsed into #323 as requested — the federation commit was cherry-picked onto the (AI-rebased) base branch, so #323 now carries all three layers. 🥰

@lopugit lopugit closed this Aug 19, 2026
@lopugit
lopugit deleted the claude/login-with-thingtime-anywhere branch August 19, 2026 11:13
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