Skip to content
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

Expanded PublicKeyCredentialCreationOptions #76

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

Joride
Copy link

@Joride Joride commented Aug 19, 2024

Hey folks,

This might be a change that brings changes to clients using the library, so please review carefully. Maybe this one requires a version-change? We could opt to maintin existing behaviour, although I would be careful with that option as well, as effectively clients are currently using an incomplete implementation of the spec? Open to hearing what direction to take and adjust as necessary.

I added some code so the options returned from beginRegistration now contain more fields from the spec.
Without these changes, I noticed that on Android, passkey-authentication could be used, however, after signup, the passkey did not seem to be stored anywhere. And so if a user was logged out, they could not sign in again with the created passkey.

With these changes, that issue is now resolved.

…m the spec

- updated implementation of `beginRegistration(..)` supply milliseconds to a new initializer
N.b. `beginRegistration(..)` now returns different fields (and so different JSON).
Comment on lines 41 to 67
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a
/// A time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a
/// hint, and may be overridden by the client.
///
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
public let timeout: Duration?
public let timeoutInMilliseconds: Int64?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Note that this was intentional so we expose a more Swift-native API for timing, and the struct encodes with milliseconds for easy integration with JS.

Copy link
Author

Choose a reason for hiding this comment

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

Got it, makes sense. I'll adjust that one.

Any thoughts on how to introduce this change, given that it might change behaviour for those who are relying on the current implementation? My thoughts:

  • A version change, I think this is quite a critical part of the lib, so might be the proper course. This would require immediate action from clients when they update the package.
  • I could come up with some layer of abstraction (maybe a protocol or a class-cluster/factory like approach) that provides current behaviour by default, and the extended behaviour when some option is specified or a specific initialiser is called. This would require zero effort from clients, but complicates the code base a little bit and keeps essentially an incompletely implemented spec alive.
  • A combination of both above, where the new default is the extended version, but current version can still be requested as well. This would require little, but non-zero effort of clients using the current version when updating the library. This could be mitigated by changing some function signature (beginRegistration comes to mind) to take an option so that when the library gets updated, clients will have to specify which behaviour the want

Copy link
Collaborator

Choose a reason for hiding this comment

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

The package is in prerelease, so breaking changes can still be made (and probably should if they improve the API). That said, not sure what about these changes (unless there are more) would incur breaking changes — it mostly seems like a collection of added properties more than anything, all of which could have sensible defaults?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, that is what I am thinking as well.
However: maybe, some implementations were relaying for example on the effect on android that I found undesirable: users being able to authenticate once, but unable to re-use that credential on subsequent logins.

@@ -21,6 +21,30 @@ import Foundation
///
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions
public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {

init(challenge: [UInt8],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure if this was meant to be public, but the internal initializer should be synthesized automatically based on default values on members. If you means to make this public, it would be better in an extension.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, note that PublicKeyCredentialCreationOptions is not intended to be instanciated directly — you are expected to request one from the manager, so many of these options likely belong there instead.

Copy link
Author

Choose a reason for hiding this comment

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

Fair point, I'll adjust this one to be in line with your suggestions.

Copy link
Author

Choose a reason for hiding this comment

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

Updated in 3a2815e1dd3bdd07bbbb57c311eac272da05bb75

}

let hints: [Hint]
enum Hint: String, Encodable
Copy link
Collaborator

@dimitribouniol dimitribouniol Aug 19, 2024

Choose a reason for hiding this comment

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

Rather than an enum, which can never take new values after the library gets a public release, please use UnreferencedStringEnumeration: https://github.com/swift-server/webauthn-swift/blob/75c32a7730188646dfae7c44adcba30242bdf220/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttachment.swift#L21

Copy link
Author

Choose a reason for hiding this comment

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

Where can I find out more about enums being problematic after public releases? I would like to learn more about this.
Meanwhile, I'll adjust according to your suggestions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The problem is described in the motivation section here: https://forums.swift.org/t/pitch-non-frozen-enumerations/68373

Copy link
Author

Choose a reason for hiding this comment

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

Ok, thank you for sharing, I'll read up on that, never thought of enums being risky 😃

Copy link
Author

Choose a reason for hiding this comment

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

Done as part of 5f8974df64c68188297a6841e77df797bb11ed70

Comment on lines 132 to 144
enum ResidentKey: String, Encodable
{
case required = "required"
case preferred = "preferred"
case discouraged = "discouraged"
}

enum UserVerification: String, Encodable
{
case required = "required"
case preferred = "preferred"
case discouraged = "discouraged"
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same for these, please use UnreferencedStringEnumeration

Copy link
Author

Choose a reason for hiding this comment

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

Done as part of 5f8974df64c68188297a6841e77df797bb11ed70


struct Extensions: Encodable
{
let credProps: Bool
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we use a CodingKeys declaration to give this type a nicer name in swift? Without looking into the spec, I have no clue what this refers to.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly, I might suggest linking directly to the spec as much as possible for all new types, like I did here, so others have an easy way back to understanding what these fields are: https://github.com/swift-server/webauthn-swift/blob/75c32a7730188646dfae7c44adcba30242bdf220/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorAttachment.swift#L15-L20

Copy link
Author

@Joride Joride Aug 19, 2024

Choose a reason for hiding this comment

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

Yeah that is a good idea to use codingkeys to have a better name in the library for this. Currently I absolutely have not enough understanding of the spec to be able to convert the below statement into something more descriptive. Any suggestions?

WebAuthn Extension Identifiers defines the initial set of extensions to be registered in the IANA Registry "WebAuthn Extension Identifier" registry established by WebAuthn-Registries.

These MAY be implemented by User-agents targeting broad interoperability.

Copy link
Collaborator

Choose a reason for hiding this comment

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

https://www.w3.org/TR/webauthn-2/#sctn-authenticator-credential-properties-extension

I would suggest credentialPropertiesExtension, set to a CredentialPropertiesExtension.RequestOptions type with .requested and .ignore options that render the appropriate value to the encoded output (either true, or the key is skipped)

Copy link
Author

Choose a reason for hiding this comment

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

Not sure I follow. Conceptually, do you mean something like the following (except instead of enum I would use UnreferencedStringEnumeration):

let credentialPropertiesExtension: CredentialPropertiesExtension
struct CredentialPropertiesExtension: Encodable
{
    let options: RequestOptions
    
    enum RequestOptions: Encodable
    {
        case requested
        case ignored
    }
}

Comment on lines 103 to 104
struct Extensions: Encodable
{
Copy link
Collaborator

@dimitribouniol dimitribouniol Aug 19, 2024

Choose a reason for hiding this comment

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

nit, based on formatting in the rest of the repo:

Suggested change
struct Extensions: Encodable
{
struct Extensions: Encodable {

Copy link
Collaborator

@dimitribouniol dimitribouniol Aug 19, 2024

Choose a reason for hiding this comment

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

Also, it might make a lot more sense to make dedicated public types for all of these in their own files, so we can properly model them in the rest of the code

Copy link
Author

Choose a reason for hiding this comment

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

Ah yeah, I'm a bit of an outlier with how I format my braces, used to having a linter 'fix' that on a pre-commit hook 😃 .

About the public types: I was thinking that could be considered clutter, as those types are not really needed (yet?) anywhere outside of this struct?
Happy to make the change of course, but: are you sure?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a suggestion from my part, as I'm not an actual maintainer, but having seen the spec in its entirety, I think its warranted, plug it keeps this file highly focused.

Copy link
Author

Choose a reason for hiding this comment

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

Also, it might make a lot more sense to make dedicated public types for all of these in their own files, so we can properly model them in the rest of the code

Done as part of 5f8974df64c68188297a6841e77df797bb11ed70. It seems the rest of the repo keeps the types mostly in the same file, so in keeping with that, the types are no dedicated public, but still live in the same file.

@@ -69,7 +69,7 @@ public struct WebAuthnManager: Sendable {
user: user,
relyingParty: .init(id: configuration.relyingPartyID, name: configuration.relyingPartyName),
publicKeyCredentialParameters: publicKeyCredentialParameters,
timeout: timeout,
timeout: (timeout?.milliseconds ?? 0) / 1000,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should still be unnecessary?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, updated in commit ee000536baa6801d50b9c4a1242f43a9be1d71f1

Joride added 3 commits August 22, 2024 15:43
…or timeout, of type `Duration`

Follow up on this discussion: swift-server#76 (comment)
… moved defaults to `beginRegistration(..)` on `WebAuthnManager`

see per suggestion: swift-server#76 (comment)
- removed some types that turned out to be redundant with existing ones
- switched from using enums to using UnreferencedStringEnumeration

addresses these discussion points
swift-server#76 (comment)
swift-server#76 (comment)
swift-server#76 (comment)
@Joride
Copy link
Author

Joride commented Oct 17, 2024

Just checking in on this one, as I'm getting closer to going live using this package.
Any (rough) e.t.a. on when this will be merged in? Any pending adjustments maybe?

@thoven87
Copy link

@dimitribouniol @Joride are they any particular reasons why this PR can't be merged? I wanted to build on this PR to expose aaguid, aaid so that relying party can filter passkey authenticators based on the fido metadata blob

@Joride
Copy link
Author

Joride commented Dec 11, 2024

From my perspective this is Ok from a functionality perspective. I've been using this change now for a few months, haven't run into any issues.

I think I addressed the various comments and suggestions, so I would be happy to see it merged.

@dimitribouniol
Copy link
Collaborator

I'll take a closer look reviewing it tomorrow morning when I get some time.

Copy link
Collaborator

@dimitribouniol dimitribouniol left a comment

Choose a reason for hiding this comment

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

Changes notwithstanding, I'll need to first make a PR that fixes testing before this can go in.

Comment on lines +73 to +79
attestation: attestation,
hints: [],
extensions: .init(credProps: true),
excludeCredentials: [],
authenticatorSelection: .init(residentKey: .preferred,
requireResidentKey: false,
userVerification: .preferred)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll need to review the spec to make sure these won't cause issues in the default case, though at a glance, this could be breaking as it specifies more than what was there currently for all cases.

@@ -21,6 +21,7 @@ import Foundation
///
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions
public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove.

Comment on lines -43 to -44
///
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please remove

Comment on lines +201 to +219
struct Extensions: Encodable {
/// Indicate that this extension is requested by the Relying Party.
/// https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension
let credProps: Bool
}

struct AuthenticatorSelectionCriteria: Encodable {
/// Specifies the extent to which the Relying Party desires to create a client-side discoverable credential.
/// https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey
let residentKey: ResidentKeyRequirement

/// Relying Parties SHOULD set this to true if, and only if, `residentKey` is set to `required`.
/// https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-requireresidentkey
let requireResidentKey: Bool

/// This member specifies the Relying Party's requirements regarding user verification for the create() operation.
/// https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-userverification
let userVerification: UserVerificationRequirement
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

These should be made public, and probably placed in their own files

Comment on lines +201 to +205
struct Extensions: Encodable {
/// Indicate that this extension is requested by the Relying Party.
/// https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension
let credProps: Bool
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't feel like the best Swift API for this, though not having looked deeper, I don't have a suggestion for improvement yet.

public let timeout: Duration?

/// Sets the Relying Party's preference for attestation conveyance. At the time of writing only `none` is
/// supported.
public let attestation: AttestationConveyancePreference

/// Use this enumeration to communicate hints to the user-agent about how a request may be best completed. These hints are not requirements, and do not bind the user-agent, but may guide it in providing the best experience by using contextual information that the Relying Party has about the request.
/// https://www.w3.org/TR/webauthn-3/#enum-hints
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor nit, but would be good to follow this convention for references:

/// - SeeAlso: [WebAuthn Leven 3 Editor's Draft §6. WebAuthn Authenticator Model](https://w3c.github.io/webauthn/#aaguid)

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