Skip to content

feat(sso): single sign-on for end users from your product#4

Merged
canghai118 merged 2 commits into
linkcraftstudio:mainfrom
canghai118:main
Jun 1, 2026
Merged

feat(sso): single sign-on for end users from your product#4
canghai118 merged 2 commits into
linkcraftstudio:mainfrom
canghai118:main

Conversation

@canghai118
Copy link
Copy Markdown
Contributor

Summary

Adds third-party product SSO so a product's already-signed-in users can reach their FeedLog feedback board without a second login. The customer's backend pre-signs a short-lived HS256 JWT and redirects the user to GET /api/sso/jwt, which verifies the token against the org's signing secrets, finds-or-creates the end user by email, mints a host-bound session, and redirects to return_to. Same-domain throughout — no cross-domain hop, no one-time-token handoff.

What's included

  • Handoff endpoint GET /api/sso/jwt — algorithm-confined HS256 verify, exp required + 24h max-TTL cap (±60s clock tolerance), find-or-create user by email, host-only session cookie, friendly /sso/error interstitial on failure (logs the real reason server-side).
  • Owner-managed signing secrets — Dashboard → Developer → Single Sign-On: create / rename / enable-disable / delete, with CRUD-style rotation (verification tries every enabled secret, so no kid). Per-org cap of 5.
  • Integration guide — endpoint, JWT payload fields (email, name, picture, exp), copy-paste Node signing snippet, TTL guidance.
  • Security guards
    • Host-binding collar — an SSO session is valid only on the org host that minted it (single-tenant deployments: no-op).
    • Identity-elevation guard — SSO sessions are blocked (403) from credential changes (set/change-password, change-email, update-user, link/unlink-account) and from the /api/auth/organization/ and /api/auth/admin/ namespaces, even if the email matches an org member/owner. Staff/dashboard surfaces 403 likewise; anonymous stays 401.

Design notes

  • Signing secrets are stored in plaintext by design — HMAC verification needs the raw value and the dashboard re-reveals them on demand (unlike a hashed, show-once bearer key). Treat the table as a high-value credential at the DB / access-control layer.
  • Identity key is email; emailVerified is set true so a later verified Google/password login links onto the same row rather than being locked out. Email change = new user (no history migration).
  • The ssoOrgId session field is input:false (clients can't forge it) and persisted via internalAdapter.createSession override; the session-cookie value is reproduced to match better-auth/better-call byte-for-byte (WebCrypto, edge-safe).

Regression testing (local, OSS single-tenant)

  • Production build (pnpm build, node preset) — clean; SSO routes + 0005 migration bundled.
  • SSO handoff e2e — valid token → 302 + host-only HttpOnly session cookie + session.ssoOrgId set + user created; expired / bad-signature / exp-too-far → /sso/error; return_to=//evil.com → falls back to /.
  • Guardsupdate-user, organization/list, admin/list-users, owner-only secrets API all 403 for SSO sessions; anonymous 401.
  • Core flows — signup/login, post, vote, comment, dashboard, SSO settings + integration guide UI — no console errors.
  • ℹ️ Errors from nuxi typecheck are all pre-existing (the repo doesn't currently gate on typecheck; vue-tsc isn't a dep); this change introduces none.

Bring your product's already-signed-in users straight into the feedback
board with no second login. Your backend hands them off through a signed
link and they arrive authenticated — ready to post, vote, and comment.

For workspace owners:
- A new Single Sign-On page in the dashboard (Developer -> Single Sign-On)
  to create and manage signing secrets, with safe rotation: create a new
  one, switch your product to it, disable the old one, delete when ready.
- A built-in integration guide with the endpoint, token fields, and a
  copy-paste backend snippet to connect quickly.

SSO users are always treated as end users: they can take part on the board
but never reach staff or admin areas and can't change account credentials,
even if their email matches a team member. A misconfigured or expired
sign-in link shows a friendly notice and continues to the board instead of
a raw error.
@canghai118 canghai118 merged commit 97bc89b into linkcraftstudio:main Jun 1, 2026
2 checks passed
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