Skip to content

feat(auth): offer a passkey once during app sign-in - #58

Merged
lorenzocorallo merged 2 commits into
mainfrom
feat/passkey-prompt
Oct 15, 2026
Merged

lorenzocorallo merged 2 commits into
mainfrom
feat/passkey-prompt

Conversation

@toto04

@toto04 toto04 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

What

People rarely visit the IdP home page; they come through an app's sign-in. Now, right after signing in on the way to an app, someone without a passkey sees one screen offering to add a passkey. Adding one or choosing Skip carries on with the same sign-in, to consent or straight back to the app.

  • Asked once: Skip stores user.passkey_prompt_declined_at (migration 0021), and the offer never shows again.
  • Not shown to people who already have a passkey.
  • Login flow keeps going: this uses Better Auth's postLogin step (/passkey), so the signed OAuth request is carried through and oauth2/continue resumes it.

Guards

  • prompt=none is never interrupted. Showing the offer there would make an app's silent sign-in fail with interaction_required.
  • The offer only appears while the session can still register a passkey, because Better Auth's freshAge refuses older sessions. freshAge is now set explicitly to the default (24h) so the two use the same value.
  • If the browser can't create passkeys, the page continues automatically and sets a browser-session cookie. Without it, continue would loop back to the offer. This isn't stored as a skip, because the person never saw the offer.
  • Opening /passkey without a signed request redirects to /.

Testing

  • vp check and vp test pass. New unit tests cover shouldOfferPasskey.
  • Tested end to end against the local Compose DB with a demo public client:
    • A dev sign-in during authorize lands on /passkey.
    • Calling continue without answering comes back to /passkey.
    • Skip records the timestamp, then goes to consent, and Allow returns a code to the app.
    • Signing in again goes straight to the app.
    • A user who has a passkey goes straight to consent.
    • The unsupported-browser cookie skips the offer.
    • prompt=none returns consent_required, not interaction_required.
  • Not tested: actually creating a passkey from this page, because I had no authenticator. It uses the same authClient.passkey.addPasskey() as the account page.

Out of scope: people who sign in directly on the IdP home page still add passkeys from the account page's Passkeys panel. The offer only appears during app sign-ins.

After signing in on the way to an application, someone without a passkey is
asked once to add one. Adding it or skipping continues the same sign-in; a skip
is recorded so the offer never returns.
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 36 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4966e39e-4e46-4671-833a-bcbf3d7ebd08
📥 Commits

Reviewing files that changed from the base of the PR and between 4a255b6 and a47c4bf.

📒 Files selected for processing: 11
  • drizzle/0021_passkey_prompt_declined.sql
  • drizzle/meta/0021_snapshot.json
  • drizzle/meta/_journal.json
  • src/auth/account.functions.ts
  • src/auth/cookies.ts
  • src/auth/index.ts
  • src/auth/passkey-prompt.test.ts
  • src/auth/passkey-prompt.ts
  • src/db/auth-schema.ts
  • src/routeTree.gen.ts
  • src/routes/passkey.tsx
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lorenzocorallo
lorenzocorallo merged commit e144167 into main Oct 15, 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.

2 participants