Skip to content

Conversation

iinuwa
Copy link

@iinuwa iinuwa commented Oct 1, 2025

🎟️ Tracking

PM-26177

📔 Objective

This is a demo implementation for discussion of a device-bound PRF passkey implementation. This implements the WebAuthn create and get operations manually inside of Swift rather than using functionality from the SDK. This is because the current FIDO authenticator API in the SDK does not allow easily injecting passkey key material from outside of the vault. Another PR showing an alternative implementation using the SDK will follow for comparison.

Note: This depends on the following changes to be merged before this can function:

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

Copy link
Contributor

github-actions bot commented Oct 1, 2025

Logo
Checkmarx One – Scan Summary & Details7f480c76-e60d-410f-9ac8-db1b5842916a

Great job! No new security vulnerabilities introduced in this pull request

import Foundation

extension Data {
public func base64UrlEncodedString(trimPadding: Bool? = true) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 Is this function really necessary when you can just .base64EncodedString().urlEncoded()? If we really care about whether or not we trim the padding, the current function can just be modified, I think?


extension Data {
public func base64UrlEncodedString(trimPadding: Bool? = true) -> String {
let shouldTrim = if trimPadding != nil { trimPadding! } else { true }
Copy link
Contributor

Choose a reason for hiding this comment

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

📝 You can just say trimPadding == true, which covers the nil case already.

🤔 But why is trimPadding even optional in the first place? It seems to me like it shouldn't be, especially if we give it a default value.

}
}

private func normalizeBase64Url(_ str: String) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 I don't think we like having global functions if at all possible. This really feels like something that should be an extension on String to me

let masterPasswordHash: String?
let otp: String?


Copy link
Contributor

Choose a reason for hiding this comment

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

⛏️ Missing a MARK for initializers


// MARK: Properties

let authRequestAccessCode: String?
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 I think these properties should all have documentation comments

}
guard let prf else { return nil }

let encoder = JSONEncoder()
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 Why not use our standard encoder?

guard let prf else { return nil }

let encoder = JSONEncoder()
let salt2 = if let salt2 = prf.1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 let salt2 = if let salt2 feels really weird to me

let record = try decoder.decode(DevicePasskeyRecord.self, from: json.data(using: .utf8)!)
identities.append(ASPasskeyCredentialIdentity(
relyingPartyIdentifier: record.rpId,
userName: record.userName!,
Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 We try to avoid force unwrapping if possible, as that crashes the app

passkeyRequest: passkeyRequest, credentialIdentity: credentialIdentity
)

Copy link
Contributor

Choose a reason for hiding this comment

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

🎨 Blank lines shouldn't have spaces in them

)
.getAssertion(request: request)
let devicePasskeyResult = try await devicePasskeyService.useDevicePasskey(for: request)
let (assertionResult, prfResult): (GetAssertionResult, Data?) = if let devicePasskeyResult {
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 I feel like this would be better as a struct than a tuple, personally

@KatherineInCode
Copy link
Contributor

Re: the above—I can't speak to the specifics of the implementation, but it looks fine. The comments mostly a bunch of papercut stuff where things are out of line with our preferred code style and documentation, along with some force-unwrapping that would probably be best to not have.

Copy link

codecov bot commented Oct 1, 2025

Codecov Report

❌ Patch coverage is 0% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.91%. Comparing base (277efe6) to head (306ae16).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...Kit/Core/Platform/Utilities/Base64UrlEncoder.swift 0.00% 26 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2009       +/-   ##
===========================================
- Coverage   81.32%   48.91%   -32.42%     
===========================================
  Files         832      289      -543     
  Lines       52457    12993    -39464     
===========================================
- Hits        42663     6355    -36308     
+ Misses       9794     6638     -3156     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@iinuwa
Copy link
Author

iinuwa commented Oct 3, 2025

Thank you for the review @KatherineInCode! This is my first time writing Swift, so your comments are very helpful. I still need to do the companion PR to compare an alternative implementation before the Auth team can consider taking on this feature at all, so I'll come back to address these comments after I've written the other PR.

@KatherineInCode
Copy link
Contributor

Thank you for the review @KatherineInCode! This is my first time writing Swift, so your comments are very helpful. I still need to do the companion PR to compare an alternative implementation before the Auth team can consider taking on this feature at all, so I'll come back to address these comments after I've written the other PR.

For sure. And I also apologize a little—I thought this was further along than it was when I dropped all my comments. For the next one I'll wait a bit until it's in a more-final state.

@iinuwa iinuwa force-pushed the auth/PM-26177/ios-device-passkey branch from 58ccc38 to 192e272 Compare October 3, 2025 13:24
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.

3 participants