-
Notifications
You must be signed in to change notification settings - Fork 26
feat: add support for PRF (eval)
#51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for PRF (eval)
#51
Conversation
WalkthroughAdds WebAuthn PRF extension support across web, iOS, and the example app: normalizes PRF inputs, wires PRF into create/get requests, marshals PRF outputs into clientExtensionResults.prf, updates TypeScript types and public signatures, and implements iOS 18+ conditional PRF handling plus a small example UI change. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant App
participant RN as RN API
participant Platform as Web/iOS
participant Auth as Platform AuthN
rect #e6f2ff
note over App,RN: Registration (create) with PRF
User->>App: Tap Create / Derive Key
App->>RN: create({ extensions:{ prf.eval:{first,second?}, largeBlob? } })
RN->>Platform: normalizePRFInputs -> invoke create()
Platform->>Auth: Create credential (PRF ext)
Auth-->>Platform: Credential + extensions { prf, largeBlob? }
Platform-->>RN: Response incl. clientExtensionResults.prf
RN-->>App: CreationResponse (prf outputs)
end
rect #eaffea
note over App,RN: Assertion (get) with PRF
User->>App: Tap Get / Derive Key
App->>RN: get({ extensions:{ prf.eval:{first,second?}, largeBlob? } })
RN->>Platform: normalizePRFInputs -> invoke get()
Platform->>Auth: Get assertion (PRF ext)
Auth-->>Platform: Assertion + extensions { prf, largeBlob? }
Platform-->>RN: Response incl. clientExtensionResults.prf
RN-->>App: GetResponse (prf outputs)
end
note over Platform: iOS: PRF handling guarded by #available(iOS 18+)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (3)
🧰 Additional context used🧬 Code graph analysis (1)ios/ReactNativePasskeysModule.swift (1)
🔇 Additional comments (6)
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. 🧪 Early access (Sonnet 4.5): enabledWe are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience. Note:
Comment |
e697c06 to
8e5773c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Nitpick comments (3)
ios/Shared.swift (1)
252-262: Consider consistent naming convention for PRF structures.The input structures use
Prf(e.g.,AuthenticationExtensionsPrfInputs) while the output structures in PasskeyResponses.swift usePRF(e.g.,AuthenticationExtensionsPRFOutputsJSON). For consistency across the codebase, consider using the same casing convention.Apply this diff for consistency:
-internal struct AuthenticationExtensionsPrfEvalInputs: Record { +internal struct AuthenticationExtensionsPRFEvalInputs: Record { @Field var first: Base64URLString @Field var second: Base64URLString? } -internal struct AuthenticationExtensionsPrfInputs: Record { +internal struct AuthenticationExtensionsPRFInputs: Record { @Field - var eval: AuthenticationExtensionsPrfEvalInputs? + var eval: AuthenticationExtensionsPRFEvalInputs? }src/utils/prf.ts (1)
7-24: Minor: Remove redundant optional chaining and consider adding documentation.The function correctly normalizes PRF inputs, but has a small redundancy:
- Line 21: The optional chaining
prf.eval?.secondis redundant sinceprf.evalis already verified to exist at line 14.- Consider adding JSDoc documentation to describe the normalization behavior and return type variations.
Apply this diff:
export function normalizePRFInputs(request: PublicKeyCredentialCreationOptionsJSON | PublicKeyCredentialRequestOptionsJSON) { const { prf } = request.extensions ?? {} if (!prf) { return } if (!prf.eval) { return {} } return { eval: { first: base64URLStringToBuffer(prf.eval.first), - second: prf.eval.second ? base64URLStringToBuffer(prf.eval?.second) : undefined + second: prf.eval.second ? base64URLStringToBuffer(prf.eval.second) : undefined } } }Optionally, add JSDoc:
/** * Normalizes PRF extension inputs by converting base64url strings to ArrayBuffers. * @param request - The credential creation or request options * @returns Normalized PRF inputs with ArrayBuffers, empty object if prf exists but eval is missing, or undefined if no prf */ export function normalizePRFInputs(request: PublicKeyCredentialCreationOptionsJSON | PublicKeyCredentialRequestOptionsJSON) { // ... }example/src/app/index.tsx (1)
76-76: Consider more specific typing for result state.The
anytype reduces type safety. For an example app demonstrating the API, consider using a union type of the possible response shapes to provide better IntelliSense and catch potential issues.Example:
const [result, setResult] = React.useState< | NonNullable<Awaited<ReturnType<typeof passkey.create>>> | NonNullable<Awaited<ReturnType<typeof passkey.get>>> | { clientExtensionResults: { prf?: any } } >();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
example/src/app/index.tsx(4 hunks)ios/PasskeyDelegate.swift(2 hunks)ios/PasskeyResponses.swift(2 hunks)ios/ReactNativePasskeysModule.swift(2 hunks)ios/Shared.swift(4 hunks)package.json(1 hunks)src/ReactNativePasskeys.types.ts(4 hunks)src/ReactNativePasskeysModule.web.ts(7 hunks)src/index.ts(3 hunks)src/utils/prf.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/utils/prf.ts (2)
src/ReactNativePasskeys.types.ts (2)
PublicKeyCredentialCreationOptionsJSON(51-61)PublicKeyCredentialRequestOptionsJSON(66-73)src/utils/base64.ts (1)
base64URLStringToBuffer(9-34)
src/index.ts (1)
src/ReactNativePasskeys.types.ts (2)
AuthenticationExtensionsLargeBlobInputs(143-153)AuthenticationExtensionsPrfInputs(121-123)
ios/ReactNativePasskeysModule.swift (1)
ios/PasskeyDelegate.swift (4)
iOS(21-26)iOS(28-31)iOS(33-39)iOS(41-190)
src/ReactNativePasskeysModule.web.ts (4)
src/ReactNativePasskeys.types.ts (2)
CreationResponse(212-220)AuthenticationExtensionsClientOutputs(157-167)src/utils/prf.ts (1)
normalizePRFInputs(7-24)src/utils/warn-user-of-missing-webauthn-extensions.ts (1)
warnUserOfMissingWebauthnExtensions(9-23)src/utils/base64.ts (1)
bufferToBase64URLString(42-53)
ios/PasskeyDelegate.swift (2)
src/ReactNativePasskeys.types.ts (3)
AuthenticationExtensionsPRFOutputsJSON(201-206)AuthenticationExtensionsPRFValuesJSON(193-196)AuthenticationExtensionsClientOutputsJSON(170-173)ios/Shared.swift (2)
serialize(346-350)serialize(354-356)
example/src/app/index.tsx (2)
android/src/main/java/expo/modules/passkeys/PasskeyOptions.kt (1)
rp(10-36)src/utils/base64.ts (1)
bufferToBase64URLString(42-53)
🔇 Additional comments (24)
package.json (1)
53-53: LGTM! Trailing newline added.This is a standard formatting change that aligns with common conventions.
ios/PasskeyResponses.swift (2)
113-116: LGTM!The PRF field addition follows the existing pattern for client extension outputs and correctly uses an optional type.
136-153: LGTM!The PRF structures correctly implement the WebAuthn specification. The field types and optionality align with the spec requirements, and the Base64URLString usage is appropriate for serialized PRF values.
ios/Shared.swift (2)
3-3: LGTM!CryptoKit import is necessary for the SymmetricKey serialization extensions added later in the file.
272-273: LGTM!The PRF field addition to client inputs follows the established pattern and correctly uses an optional type.
example/src/app/index.tsx (2)
91-94: LGTM!The PRF extension is correctly added to the registration request. Using an empty object signals PRF support without providing eval inputs, which aligns with the WebAuthn PRF extension behavior during registration.
214-216: LGTM!The new button is correctly wired to the
deriveKeyhandler and has a clear, descriptive label.src/index.ts (2)
5-5: LGTM!The import of
AuthenticationExtensionsPrfInputscorrectly expands the public type surface to support PRF extension inputs.
30-30: LGTM!The
prfextension is correctly added as an optional field alongsidelargeBlobin thecreaterequest extensions.ios/PasskeyDelegate.swift (4)
73-76: LGTM!The
clientExtensionResultscorrectly includes bothlargeBlobandprffields for the registration response.
145-148: LGTM!The
clientExtensionResultsfor assertion correctly includes bothlargeBlobandprffields. Note thatprf.enabledis correctly omitted in the assertion flow, as it only applies to registration per the WebAuthn spec.
133-143: Optional handling is correct—SymmetricKey?extension exists.Verification confirms that
Shared.swiftdefines an extension onSymmetricKey?(lines 353-357) providing aserialize()method that returnsString?. The code at line 138 correctly calls$0.second.serialize()wheresecondisSymmetricKey?, and the extension handles the optional case by mapping over it. Both registration (line 66) and assertion (line 138) flows use the same pattern consistently.No changes are needed.
Likely an incorrect or invalid review comment.
60-71: Review comment addresses non-existent issue—code correctly handles optionalsecondvalue.The code at line 66 (
it.second.serialize()) and line 138 ($0.second.serialize()) is correct. The codebase includes an extension onOptional<SymmetricKey>(lines 353-357 in Shared.swift) that providesfunc serialize() -> String?, allowingserialize()to be called directly on optional values without requiring optional chaining. The extension handles the nil case internally by returningOptional<String>.This is a valid Swift pattern where methods are extended on the Optional type itself, making the original concern about runtime crashes or compilation failures unfounded.
Likely an incorrect or invalid review comment.
src/ReactNativePasskeysModule.web.ts (8)
14-14: LGTM! Typo fix.The rename from
CreationReponsetoCreationResponsecorrects a typo in the type name.
16-16: LGTM!The import of
normalizePRFInputscorrectly brings in the utility function for PRF input handling.
52-55: LGTM!The PRF extension is correctly wired into the create request via
normalizePRFInputs, which converts base64URL strings to ArrayBuffers as required by the native WebAuthn API.
63-63: LGTM!Destructuring
prffrom extensions allows separate handling of PRF results for serialization.
87-93: LGTM!The PRF results are correctly serialized in the registration response:
- The
enabledfield is included (registration-only per spec)- Both
firstandsecondvalues are properly converted from ArrayBuffer to base64URL strings- Optional chaining ensures safe handling when PRF results are absent
114-114: LGTM!The PRF extension is correctly wired into the get request via
normalizePRFInputs.
143-143: LGTM!Destructuring pattern correctly separates PRF results from other extension outputs.
167-172: LGTM!The PRF results are correctly serialized in the assertion response:
- The
enabledfield is correctly omitted (only present during registration per spec)- Both
firstandsecondvalues are properly converted from ArrayBuffer to base64URL strings- Optional chaining ensures safe handling when PRF results are absent
src/ReactNativePasskeys.types.ts (3)
135-135: LGTM!The addition of the optional
prffield toAuthenticationExtensionsClientInputscorrectly integrates PRF extension support into the client inputs interface.
156-167: LGTM!The PRF extension output structure correctly mirrors the
largeBlobpattern, transforming Base64URLString values to ArrayBuffer for native representation. The use ofOmitand type intersection maintains consistency with the existing codebase style.
212-219: LGTM! Typo fix and spec alignment.The interface rename from
CreationReponsetoCreationResponsecorrects a typo. ThegetPublicKey()return type change fromUint8Array | nulltoArrayBuffer | nullaligns with the WebAuthn specification and maintains consistency with other native type representations in this file (e.g.,largeBlob.blob,prf.results.first).Note: These are breaking changes for consumers, but they represent necessary corrections.
|
awesome @sparten11740 thanks for the pr will take a look at this tomorrow morning 👍 |
|
Nice looks good locally! I will do some work on the android side & publish both together if that is cool 👍 |
|
Sounds good 👍 |
Add support for the WebAuthn PRF extension to derive values such as symmetric encryption keys from the passkey.
This PR deals with the more common case of requesting derived values independent of the credential used (eval). The spec also allows for supplying inputs by credential id through the evalByCredential property. If you accept contributions and this PR lands, I am happy to add support for
evalByCredentialin a follow up PR.Towards #31
Test Plan
Simulator.Screen.Recording.-.iPhone.16.Pro.Max.-.2025-09-29.at.20.49.55.mp4
Summary by CodeRabbit
New Features
Refactor