I have been working on a Rails 7 app using webauthn-ruby gem for 2FA. In the frontend we use WebAuthnJSON. When logging in, first there is a step with email/password, after which we find the existing credentials and a user will need to authenticate with webauthn.
The problem I have been running into is, no matter what settings I use, in Windows (chrome), users always get prompted for a security key (usb) or with other settings they get prompted for windows hello sign in when they authenticate. After you cancel that step, you get to the normal chrome modal where you can select a a device to authenticate with. We want to skip the windows step, or rather enable users to set up an android device, but can't figure out how to.
This issue can be reproduced on https://webauthn.cedarcode.com/.
However if you test on https://webauthn.io, this issue is not present.
Is this because of this gem or is it related to WebAuthnJSON or are the settings incorrect?
This happens using the default settings and pretty much any other settings.
Server Registration:
create_options = WebAuthn::Credential.options_for_create(
exclude: @authenticatable.webauthn_credentials.pluck(:external_id),
user: {
id: @authenticatable.webauthn_id,
display_name: @authenticatable.email,
name: @authenticatable.full_name
},
pub_key_cred_params: [
{
type: 'public-key',
alg: -7 # "ES256" as registered in the IANA COSE Algorithms registry
},
{
type: 'public-key',
alg: -257 # Value registered by this specification for "RS256"
}
],
authenticator_selection: {
resident_key: 'required', # This setting specifically makes windows hello not pop-up
user_verification: 'preferred'
},
attestation: 'none'
)
Client registration:
WebAuthnJSON.get({ "publicKey": data }).then(async function(credential) {
const request = new FetchRequest("post", _this.callbackValue, { body: JSON.stringify(credential) })
const response = await request.perform()
if (response.ok) {
const data = await response.json
window.Turbo.visit(data.redirect, { action: 'replace' })
} else {
console.log("An error occured while authenticating Webauthn", response);
}
}).catch(function(error) {
console.log("An error occured while authenticating Webauthn", error);
});
Server authentication:
get_options = WebAuthn::Credential.options_for_get(
allow: @authenticatable.webauthn_credentials.pluck(:external_id),
user_verification: 'discouraged'
)
Client authentication:
WebAuthnJSON.get({ "publicKey": data }).then(async function(credential) {
const request = new FetchRequest("post", _this.callbackValue, { body: JSON.stringify(credential) })
const response = await request.perform()
if (response.ok) {
const data = await response.json
window.Turbo.visit(data.redirect, { action: 'replace' })
} else {
console.log("An error occured while authenticating Webauthn", response);
}
}).catch(function(error) {
console.log("An error occured while authenticating Webauthn", error);
});
Specifically setting the resident_key in the server at registration to required removes the step from the registration flow, but it doesn't remove the windows hello or security key step in authentication, so my problem persists.
Chrome on Windows has a problem because interactions with a phone have to be handled by Chrome (because Windows doesn't implement that until the very latest Windows 11 update, which I think is still in preview only). But interactions with security keys and Windows Hello have to be handled by Windows itself.
So Chrome has to take a guess about which UI to invoke on most versions of Windows.
The main control is the authenticatorAttachment
in authenticator_selection
, which it doesn't look like you are currently setting. The value of resident_key
will also have an impact and that value isn't set currently on your referenced site.
I don't have a Windows machine to hand today, but try setting authenticatorAttachment
to cross-platform
and residentKey
to required
.
(On https://webauthn.io/ you can click “Advanced settings” to set these options.)
WebAuthn level three will have a hints
parameter to allow this to be better controlled by sites, but it's not ready yet.