Skip to content

Add passkey authentication and responsive team navigation - #11

Merged
theNEXlevel merged 1 commit into
mainfrom
feat/passkeys
Aug 10, 2026
Merged

Add passkey authentication and responsive team navigation#11
theNEXlevel merged 1 commit into
mainfrom
feat/passkeys

Conversation

@theNEXlevel

Copy link
Copy Markdown
Contributor

Enables passkeys via Better Auth, plus a few UI and tooling fixes made along the way.

Passkeys

  • Adds @better-auth/passkey (bumps better-auth 1.6.23 → 1.6.26, where the plugin moved into its own package).
  • Adds the Passkey model and an additive migration — no changes to existing tables.
  • Adds a passkey management dialog (list / add / remove) to the user menu, and passkey sign-in with browser autofill (conditional UI) on /signin.

Subdomain scoping

rpID is pinned to the BETTER_AUTH_URL hostname, so passkeys registered on template.c4g.dev are scoped to that host and are never offered to sibling apps on other c4g.dev subdomains. Verified against a running server:

BETTER_AUTH_URL rpId returned
http://localhost:3000 localhost
https://template.c4g.dev template.c4g.dev

PASSKEY_RP_ID can override this if shared sign-in across subdomains is ever wanted — but that would let any sibling subdomain assert those credentials.

Important

BETTER_AUTH_URL must be set in production. The plugin's fallback when it is unset is localhost, which fails confusingly rather than loudly.

UI

  • Team pages: vertical nav on tablet/desktop, shadcn Select on mobile (md breakpoint).
  • Declares color-scheme per theme. The theme only swapped CSS variables, so browser-rendered UI (select popups, scrollbars) always painted light regardless of theme — this affects every page, not just the new dropdown.
  • Adds the Google mark to the Google sign-in/sign-up buttons; lucide ships no brand icons, so it is a local component.

Tooling

pnpm format was failing on 9 files before this branch. Passing only --ignore-path .gitignore replaces Prettier's default ignore list, so .prettierignore was never read. Now passes both paths, ignores the lockfile and generated output, and reflows the files that had drifted from the pinned Prettier 3.8.

Notes for review

  • The Passkeys menu entry is hidden while impersonating: the passkey endpoints resolve the real session rather than the impersonation override, so a passkey added there would attach to the admin's own account. Not an escalation risk, just misleading.
  • Passkeys are always a second credential — an account still needs a password or Google to register the first one, which is also what keeps recovery working.

Testing

pnpm format, pnpm lint, tsc --noEmit, 45 tests, and pnpm build all pass.

Verified in Chrome: mobile dropdown opens, navigates, and themes correctly in both light and dark; the desktop sidebar and mobile dropdown swap correctly at the breakpoint; passkey endpoints return 401 rather than 500 once the Prisma client includes the model.

Not verified: a full register/sign-in ceremony with a real authenticator. Worth exercising before merge.

🤖 Generated with Claude Code

Enable passkeys via Better Auth's passkey plugin, alongside a few
UI and tooling fixes made along the way.

Passkeys:
- Add @better-auth/passkey (bumps better-auth 1.6.23 -> 1.6.26, where
  the plugin moved into its own package)
- Add the Passkey model and an additive migration
- Pin rpID to the BETTER_AUTH_URL hostname so passkeys stay scoped to
  this app rather than a shared parent domain, and document the
  optional PASSKEY_RP_ID override
- Add a passkey management dialog (list/add/remove) to the user menu,
  hidden while impersonating since the endpoints resolve the real
  session rather than the impersonation override
- Add passkey sign-in plus browser autofill (conditional UI) on /signin
- Surface auth errors without inventing a cause (src/lib/auth-errors.ts)

UI:
- Team pages: vertical nav on tablet/desktop, shadcn Select on mobile
- Declare color-scheme per theme so browser-rendered UI (select popups,
  scrollbars) follows dark mode instead of always painting light
- Add the Google mark to the Google sign-in/sign-up buttons

Tooling:
- Pass both ignore paths to Prettier. Passing only --ignore-path
  .gitignore replaced the default list, so .prettierignore was never
  read; ignore the lockfile and generated output, and reflow the files
  that had drifted from the pinned Prettier 3.8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@theNEXlevel
theNEXlevel merged commit dcc8c5c into main Aug 10, 2026
1 check passed
@theNEXlevel
theNEXlevel deleted the feat/passkeys branch August 10, 2026 05:11
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