Skip to content

feat: add passkey registration endpoint#41

Merged
sirily11 merged 1 commit into
mainfrom
scope
May 26, 2026
Merged

feat: add passkey registration endpoint#41
sirily11 merged 1 commit into
mainfrom
scope

Conversation

@sirily11

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings May 26, 2026 09:59
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
rxlab-auth Ready Ready Preview, Comment May 26, 2026 9:59am

Request Review

@autopilot-project-manager autopilot-project-manager Bot added the enhancement New feature or request label May 26, 2026
@sirily11 sirily11 enabled auto-merge (squash) May 26, 2026 09:59
@sirily11 sirily11 merged commit 2fbd39f into main May 26, 2026
4 checks passed
@sirily11 sirily11 deleted the scope branch May 26, 2026 10:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds native (non-browser) OAuth passkey flows (WebAuthn) for first-party/native clients, including request validation and token issuance, while refactoring duplicated token-issuance logic into a shared helper.

Changes:

  • Added /api/oauth/passkey/* options + verify endpoints for native passkey registration and authentication.
  • Centralized refresh/access/id token issuance into issueOAuthTokenResponse, and updated password grant + native signup to use it.
  • Extended WebAuthn challenge storage in Redis to support native challenge types bound to clientId, plus added schema/tests for the new request bodies.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/validations/oauth.ts Adds Zod schemas/types for native passkey request payloads.
lib/validations/oauth.test.ts Adds schema validation tests for the new passkey request schemas.
lib/redis/index.ts Extends WebAuthnChallengeData to support native passkey flow metadata.
lib/oauth/native-client.ts Adds shared gating (validateNativeClient) and scope resolution helper for native OAuth flows.
lib/oauth/issue-tokens.ts Introduces a canonical helper to issue access/id/refresh tokens and persist refresh tokens.
app/api/oauth/token/route.ts Refactors password grant to use the shared token issuance helper.
app/api/oauth/token/password-grant.test.ts Updates Redis module mocks to match exported surface used by token route tests.
app/api/oauth/signup/route.ts Refactors E2E token issuance path to use the shared helper.
app/api/oauth/passkey/register/options/route.ts New endpoint to generate/store native registration options + challenge.
app/api/oauth/passkey/register/verify/route.ts New endpoint to verify attestation, create user/passkey, and return OAuth tokens.
app/api/oauth/passkey/register/verify/route.test.ts Tests for native registration verify flow and error cases.
app/api/oauth/passkey/authenticate/options/route.ts New endpoint to generate/store native authentication options + challenge.
app/api/oauth/passkey/authenticate/verify/route.ts New endpoint to verify assertion, update passkey counter, and return OAuth tokens.
app/api/oauth/passkey/authenticate/verify/route.test.ts Tests for native authentication verify flow and error cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await storeWebAuthnChallenge(sessionId, challengeData);

return NextResponse.json({
...registrationOptions,
await storeWebAuthnChallenge(sessionId, challengeData);

return NextResponse.json({
...authenticationOptions,
Comment on lines +33 to +37
if (client.signInPermission !== "all") {
return {
ok: false,
response: NextResponse.json(
{
Comment on lines +126 to +176
await db.transaction(async (tx) => {
await tx.insert(users).values({
id: userId,
email,
passwordHash: null,
displayName,
avatarSeed,
emailVerified: true,
createdAt: now,
updatedAt: now,
});

await tx.insert(passkeys).values({
id: cred.id,
userId,
name: "Passkey",
publicKey: base64UrlEncode(cred.publicKey),
counter: cred.counter,
deviceType: credentialDeviceType,
backedUp: credentialBackedUp,
transports:
(data.credential.response as { transports?: string[] }).transports &&
Array.isArray(
(data.credential.response as { transports?: string[] }).transports,
)
? JSON.stringify(
(data.credential.response as { transports: string[] })
.transports,
)
: null,
createdAt: now,
});
});

await deleteWebAuthnChallenge(data.session_id);

const tokenResponse = await issueOAuthTokenResponse({
user: {
id: userId,
email,
emailVerified: true,
displayName,
username: null,
avatarSeed,
avatarUrl: null,
},
client,
scopes: requestedScopes,
});

return NextResponse.json(tokenResponse, { status: 201 });
Comment on lines +123 to +127
.set({
counter: verification.authenticationInfo.newCounter,
lastUsedAt: new Date(),
})
.where(eq(passkeys.id, passkey.id));
Comment on lines +21 to +25
export async function POST(request: NextRequest) {
let body: unknown;
try {
body = await request.json();
} catch {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants