Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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 { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.