Conversation
WalkthroughAdds passkey authentication: new DB migration creating Changes
sequenceDiagram
participant Client as "Client (Browser)"
participant Server as "App Server\n(auth plugin)"
participant DB as "Database"
participant PasskeyLib as "passkey lib\n(`@better-auth/passkey`)"
rect rgba(63,81,181,0.5)
Client->>Server: Initiate passkey registration/login
Server->>PasskeyLib: Create challenge / verify response
PasskeyLib-->>Server: Challenge / Verification result
Server->>DB: Insert/Update `auth_passkey` record (credential, user_id, counters, meta)
DB-->>Server: ACK
Server-->>Client: Success response (auth completed)
end
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@package.json`:
- Line 41: Update the better-auth dependency in package.json to match the
`@better-auth/passkey` peer requirement: change "better-auth" from ^1.4.15 to
^1.4.17 so it aligns with "@better-auth/passkey": "^1.4.17" and avoids a
peerDependency mismatch; ensure both dependency entries (better-auth and
`@better-auth/passkey`) use the same major.minor.patch range (^1.4.17) and run
npm/yarn install to validate.
In `@src/auth/index.ts`:
- Around line 23-29: The drizzle adapter schema in src/auth/index.ts is missing
the passkey table mapping needed by `@better-auth/passkey`; update the
configuration passed to drizzleAdapter (the database: drizzleAdapter(db, {
provider: "pg", schema: { ... } }) call) to include a passkey property, e.g. add
passkey: SCHEMA.AUTH.passkey alongside user, account, session, and verification
so the adapter knows about the passkey table.
- Around line 18-21: Replace the hard-coded rpID in the passkey(...) call with a
derived hostname from the configured base URL: read env.PUBLIC_URL, parse it
(e.g., via new URL(env.PUBLIC_URL).hostname) and use that value as rpID (with a
safe fallback to the current request origin or a default if parsing fails).
Update the passkey(...) invocation in src/auth/index.ts so rpID is computed at
runtime from env.PUBLIC_URL rather than the literal "polinetwork.org", keeping
rpName as-is.
🧹 Nitpick comments (3)
drizzle/0006_uneven_lord_hawal.sql (1)
1-13: Consider makingcreated_atNOT NULL for consistency.The
created_atcolumn (line 11) is nullable, while other auth tables (auth_users,auth_sessions,auth_accounts) definecreated_atas NOT NULL. If there's no specific reason for passkeys to have a nullable creation timestamp, consider adding the NOT NULL constraint for consistency.Suggested fix
- "created_at" timestamp, + "created_at" timestamp NOT NULL,src/db/schema/auth/auth.ts (2)
85-103: Table naming inconsistency:"passkey"(singular) vs other tables using plural.Other tables use plural names (
"users","sessions","accounts","verifications"), but this table uses singular"passkey". Consider using"passkeys"for consistency across the auth schema.Suggested fix
export const passkey = createTable.auth( - "passkey", + "passkeys", {Note: This would require updating the SQL migration to use
auth_passkeysas well.
99-100: Consider makingcreatedAtnon-nullable to align with other auth tables.The passkey table has a nullable
createdAt, while all other auth tables (user,session,account,verification) use.notNull(). The absence ofupdatedAtfollows the better-auth passkey plugin schema (which does not include this field), so that's intentional. However, the nullablecreatedAtinconsistency should be addressed:Suggested fix
transports: text("transports"), - createdAt: timestamp("created_at"), + createdAt: timestamp("created_at").notNull(), aaguid: text("aaguid"),
| }, | ||
| "dependencies": { | ||
| "@azure/identity": "^4.13.0", | ||
| "@better-auth/passkey": "^1.4.17", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@better-auth/passkey peerDependencies better-auth compatibility
💡 Result:
@better-auth/passkey is version-pinned to better-auth via peerDependencies, so you generally need to keep the versions in lockstep.
For example, @better-auth/passkey@1.4.15 declares these peer deps (must be satisfied by your app, not installed automatically):
better-auth: 1.4.15@better-auth/core: 1.4.15@better-auth/utils: 0.3.0@better-fetch/fetch: 1.1.21better-call: 1.1.8nanostores: ^1.0.1 [1]
Compatibility rule of thumb: use the same version number for better-auth, @better-auth/core, and @better-auth/passkey (e.g., all 1.4.15). If your project is on a newer better-auth (Snyk reports 1.4.17 as latest for better-auth), you’ll likely need the matching @better-auth/passkey release (or you’ll hit peer-dep conflicts). [2]
Where to check your exact version’s peer deps: the plugin’s published package.json on jsDelivr for that specific version. [1]
Sources: [1] [2]
Align better-auth version with @better-auth/passkey.
@better-auth/passkey is version-pinned to better-auth via peerDependencies and requires matching versions. Adding @better-auth/passkey ^1.4.17 while better-auth is ^1.4.15 risks a peer dependency mismatch—if better-auth resolves to 1.4.15, the passkey package will fail due to incompatible peer dependencies. Update better-auth to ^1.4.17 to ensure version alignment.
🤖 Prompt for AI Agents
In `@package.json` at line 41, Update the better-auth dependency in package.json
to match the `@better-auth/passkey` peer requirement: change "better-auth" from
^1.4.15 to ^1.4.17 so it aligns with "@better-auth/passkey": "^1.4.17" and
avoids a peerDependency mismatch; ensure both dependency entries (better-auth
and `@better-auth/passkey`) use the same major.minor.patch range (^1.4.17) and run
npm/yarn install to validate.
| passkey({ | ||
| rpID: "polinetwork.org", | ||
| rpName: "PoliNetwork APS", | ||
| }), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the src/auth/index.ts file to see the full context
cat -n src/auth/index.ts | head -50Repository: PoliNetworkOrg/backend
Length of output: 1888
🏁 Script executed:
# Check if env.PUBLIC_URL is defined and used elsewhere
rg "PUBLIC_URL" -A 2 -B 2Repository: PoliNetworkOrg/backend
Length of output: 533
🏁 Script executed:
# Look for `@better-auth` package info and configuration patterns
cat -n package.json | grep -A 5 -B 5 "better-auth"Repository: PoliNetworkOrg/backend
Length of output: 782
🏁 Script executed:
# Search for any other passkey or rpID configuration in the codebase
rg "rpID|rpName" -A 2 -B 2Repository: PoliNetworkOrg/backend
Length of output: 280
🌐 Web query:
@better-auth passkey rpID configuration requirements
💡 Result:
For Better Auth’s @better-auth/passkey plugin, rpID must follow standard WebAuthn “Relying Party ID” rules:
- Set
rpIDto a domain name that matches your auth server’s origin domain (the public site/domain users are authenticating to).localhostis allowed for local development. [1] - It must be an “effective domain” (registrable domain) or a subdomain of it: you may drop labels from the left, e.g.
www.example.com→www.example.comorexample.com. [1] - It cannot be a public suffix / eTLD (e.g.,
comis invalid). [1] - Related config: set
originto the exact origin where your Better Auth server is hosted (scheme + host + optional port), with no trailing slash. [1]
Sources: Better Auth Passkey plugin docs. [1]
🌐 Web query:
WebAuthn rpID effective domain matching requirement
💡 Result:
In WebAuthn, the RP ID (rpId) must domain-match the calling page’s origin as follows:
- The RP ID is a valid domain string (host name only — no scheme, port, or path). [1]
- By default, the RP ID is the caller origin’s effective domain. [1]
- If you override it, the RP ID must be either:
- exactly equal to the origin’s effective domain, or
- a registrable domain suffix of the origin’s effective domain (i.e., you can broaden scope to a parent registrable domain, but you can’t choose a sibling or an unrelated domain). [1]
- The origin’s scheme must be
https(port is unrestricted). [1] - During authentication, the authenticator/browser ensures the ceremony uses the same
rpIdthe credential was registered under. [1][2]
Example (from the spec): for origin https://login.example.com:1337, valid RP IDs are login.example.com (default) and example.com, but not m.login.example.com and not com. [1]
Sources:
[1] W3C WebAuthn Level 2 Recommendation (RP ID / effective domain + registrable-suffix rules) (w3.org)
[2] MDN: PublicKeyCredentialRequestOptions.rpId (must match credential’s rpId; defaults to origin domain) (developer.mozilla.org)
Citations:
- 1: https://www.w3.org/TR/webauthn-2/
- 2: https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialRequestOptions?utm_source=openai
🏁 Script executed:
# Check if there's any documentation or comments about passkey/rpID in the repo
rg "passkey|rpID|rpName" -B 5 -A 5 --type ts --type jsRepository: PoliNetworkOrg/backend
Length of output: 3073
🏁 Script executed:
# Check the betterAuth configuration docs or types
rg "baseURL|trustedOrigins" src/auth/index.ts -B 3 -A 3Repository: PoliNetworkOrg/backend
Length of output: 239
🏁 Script executed:
# Look for any test files or configuration examples
find . -name "*.ts" -o -name "*.js" | xargs grep -l "passkey" 2>/dev/null | head -10Repository: PoliNetworkOrg/backend
Length of output: 114
Derive rpID from baseURL to prevent WebAuthn registration failures across environments.
Hard-coding rpID to "polinetwork.org" conflicts with the configurable baseURL (set to env.PUBLIC_URL). Per WebAuthn spec, rpID must match the origin's effective domain—if PUBLIC_URL points to localhost, a staging domain, or any other host during development/preview, the hard-coded rpID will cause passkey registration to fail. Extract the hostname from env.PUBLIC_URL to make rpID match the active origin.
Example fix
+const rpID = new URL(env.PUBLIC_URL).hostname
+
passkey({
- rpID: "polinetwork.org",
+ rpID,
rpName: "PoliNetwork APS",
}),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| passkey({ | |
| rpID: "polinetwork.org", | |
| rpName: "PoliNetwork APS", | |
| }), | |
| const rpID = new URL(env.PUBLIC_URL).hostname | |
| passkey({ | |
| rpID, | |
| rpName: "PoliNetwork APS", | |
| }), |
🤖 Prompt for AI Agents
In `@src/auth/index.ts` around lines 18 - 21, Replace the hard-coded rpID in the
passkey(...) call with a derived hostname from the configured base URL: read
env.PUBLIC_URL, parse it (e.g., via new URL(env.PUBLIC_URL).hostname) and use
that value as rpID (with a safe fallback to the current request origin or a
default if parsing fails). Update the passkey(...) invocation in
src/auth/index.ts so rpID is computed at runtime from env.PUBLIC_URL rather than
the literal "polinetwork.org", keeping rpName as-is.
No description provided.