-
Notifications
You must be signed in to change notification settings - Fork 24
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
base: main
Are you sure you want to change the base?
Expanded PublicKeyCredentialCreationOptions
#76
Conversation
…m the spec - updated implementation of `beginRegistration(..)` supply milliseconds to a new initializer N.b. `beginRegistration(..)` now returns different fields (and so different JSON).
/// 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? |
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.
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.
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.
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
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.
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?
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.
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], |
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.
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.
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.
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.
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.
Fair point, I'll adjust this one to be in line with your suggestions.
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.
Updated in 3a2815e1dd3bdd07bbbb57c311eac272da05bb75
} | ||
|
||
let hints: [Hint] | ||
enum Hint: String, Encodable |
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.
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
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.
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.
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.
The problem is described in the motivation section here: https://forums.swift.org/t/pitch-non-frozen-enumerations/68373
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.
Ok, thank you for sharing, I'll read up on that, never thought of enums being risky 😃
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.
Done as part of 5f8974df64c68188297a6841e77df797bb11ed70
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" | ||
} |
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.
Same for these, please use UnreferencedStringEnumeration
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.
Done as part of 5f8974df64c68188297a6841e77df797bb11ed70
|
||
struct Extensions: Encodable | ||
{ | ||
let credProps: Bool |
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.
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.
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.
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
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.
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.
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.
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)
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.
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
}
}
struct Extensions: Encodable | ||
{ |
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.
nit, based on formatting in the rest of the repo:
struct Extensions: Encodable | |
{ | |
struct Extensions: Encodable { |
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.
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
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.
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?
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.
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.
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.
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, |
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.
This should still be unnecessary?
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.
Yes, updated in commit ee000536baa6801d50b9c4a1242f43a9be1d71f1
…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)
Just checking in on this one, as I'm getting closer to going live using this package. |
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.