Closed sameh-amwal closed 2 years ago
I'm going to suggest that this is an iOS 16 issue. I tested the following workflow on both https://webauthn.io and https://example.simplewebauthn.dev, two sites I've authored that both support conditional UI via SimpleWebAuthn:
In Safari 16.1 on macOS Ventura, both sites operated as expected; no error was thrown by the aborting of the conditional UI request and subsequent attempt to call navigator.credentials.get()
.
In Safari on iOS 16.0.2, both sites errored out as you described after Step 2. I could still successfully complete the OS's WebAuthn request for Face ID, but the request to .get()
never resolved and so the credential never made it to the server.
If you have the Ventura beta running on something then I'd suggest submitting an issue via the Feedback Assistant application. It'll let you submit issues for iOS as well. I may do this as well in the next day or two.
Oh, fascinating, it's the second authentication attempt, via modal UI, that throws the NotAllowedError
on iOS 16:
But iOS goes ahead and still prompts you for the WebAuthn interaction 🤔
I submitted feedback and raised it with someone at Apple (as best I can via Twitter lol)
Here's a basic HTML reproduction page I created and included in my feedback, to prove it's not a SimpleWebAuthn issue :)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conditional UI iOS 16 Bug</title>
</head>
<body>
<button id="btnAuth">Authenticate</button>
<script>
const abortController = new AbortController();
async function startModalUIAuthentication() {
abortController.abort('Starting modal auth attempt');
try {
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([1,2,3,4]),
},
});
console.log(credential);
} catch (err) {
// iOS 16 will immediately throw a NotAllowedError here
// but still prompt for WebAuthn interaction
console.error('Error with modal UI auth:', err);
}
}
async function startConditionalUIAuthentication() {
console.log('Starting conditional UI auth attempt');
try {
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([1,2,3,4]),
},
mediation: 'conditional',
signal: abortController.signal,
});
} catch (err) {
console.error('Error with conditional UI auth:', err);
}
}
document.querySelector('#btnAuth').addEventListener('click', startModalUIAuthentication);
startConditionalUIAuthentication();
</script>
</body>
</html>
I am not sure that it is a bug in iOS 16 or it is an implementation issue due to the complexity of abort signal. Maybe startModalUIAuthentication
should only be called after the conditional one has been completely aborted as in this example code? That way there is no overlap at all between the 2 calls to navigator.credentials.get
. The second call happens after abort is completed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conditional UI iOS 16 Bug</title>
</head>
<body>
<button id="btnAuth">Authenticate</button>
<script>
const abortController = new AbortController();
async function startModalUIAuthentication() {
try {
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([1,2,3,4]),
},
});
console.log(credential);
} catch (err) {
// iOS 16 will immediately throw a NotAllowedError here
// but still prompt for WebAuthn interaction
console.error('Error with modal UI auth:', err);
}
}
async function startConditionalUIAuthentication() {
console.log('Starting conditional UI auth attempt');
try {
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([1,2,3,4]),
},
mediation: 'conditional',
signal: abortController.signal,
});
} catch (err) {
if (error.name == "AbortError") {
console.log("request aborted, starting vanilla request");
startModalUIAuthentication();
return;
}
console.error('Error with conditional UI auth:', err);
}
}
document.querySelector('#btnAuth').addEventListener('click', () => abortController.abort('Starting modal auth attempt') );
startConditionalUIAuthentication();
</script>
</body>
</html>
I'm converting this into a Discussion because the issue seems to exist pretty firmly at the browser/OS level.
And if we start discussing implementation questions like, "should the modal UI get triggered from an AbortError detected when Conditional UI errors out", that's going to still be something handled by a project using SimpleWebAuthn as opposed to anything I could address within this library 🤔
Hello again, I have tested the conditional UI support in the browser library and it is working great both on Safari 16.1 on Mac and Chrome Canary. however there seems to be a problem when testing it on mobile safari on iOS 16.1 simulator (doesn't seem to work on existing iOS 16 simulator). It does seem that when there is an outstanding
startAuthentication
call withuseBrowserAutofill
true, if the traditional webauth is used for signing in, it aborts the outstanding conditional UI but it starts the new request before the aborted request being fully aborted resulting in that call failing. Calling thestartAuthentication
method again in exactly the same way but without outstanding request works fine then.Here is a successful conditional UI sign in
https://user-images.githubusercontent.com/100665288/193427560-d15eb783-c3b8-49ab-a769-b7da948be572.mov
Here is the traditional webauth sign in. Notice the first attempt fails with error at 00:05
https://user-images.githubusercontent.com/100665288/193427498-6fd07c52-e5cc-4bf4-b781-a29f0e825f69.mov
In the example code here it seems the calling for normal request happens at the error handling of aborting the conditional one. this seems convoluted and not sure if this is really needed to handle the abort correctly or not