MasterKale / SimpleWebAuthn

WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
https://simplewebauthn.dev
MIT License
1.63k stars 138 forks source link

Registration doesn't work on Android - pending promise from navigator.credentials.create #169

Closed thechrisps closed 2 years ago

thechrisps commented 2 years ago

First, thanks for this project, its a really nice simple way to learn WebAuthn, and works great on Chrome 96 on Windows.

However, on Android (Chrome Mobile or Firefox), registration doesn't work. When navigator.credentials.create is called with the publicKey object (containing the various params), a pending Promise is returned, but it is never fulfilled. Therefore, it just sits there forever. If you click "Authenticate," then it just says an operation is pending

On Chrome Android, this manifests as Clicking "Register", the debug window getting the options displayed and nothing else. On Firefox Android, when you click "Register," it pops up Android's select authenticator option (bluetooth, nfc, lock screen), but when you select one, nothing happens, like Chrome.

I have tried changing the publicKey params to be identical to the params used by webauthn.io (which works fine on Chrome and Firefox for Android). However, the same thing happens. This is leading me to suspect that there is something about the site, or header options that stops it working. But I really can't figure out what.

Any thoughts? Thanks!

MasterKale commented 2 years ago

@thechrisps Thank you for the detailed information. Can you include more about which version of Android and browsers you're experiencing this with? And would you be able to create a minimal reproduction of the issue? Specifically how you're calling the various SimpleWebAuthn methods.

And finally what URL are you using when you host the server to access with your phone? WebAuthn requires a secure context to operate correctly, so you may be suffering from this restriction if you're not hosting your server over https:// when you access it from your phone.

thechrisps commented 2 years ago

Hey, thanks for the reply. Reading it made me think of something; it is using HTTPS, but running on a custom port (not 443). Changing everything back to 433, it works fine :). However, really I need to run it on a custom port.

Looking into this, I had changed your code to load the port number from a dotenv. All fine. I had to set the RPID to mydomain.com, as mydomain.com:myport results in an error "The relying party ID is not a registrable domain suffix of, nor equal to the current domain." Looking at the spec, this makes sense.

This means that the options look like: "rp": { "name": "SimpleWebAuthn Example", "id": "mydomain.com" },

This works fine on Chrome 96 desktop. But on Chrome 97 mobile and Firefox 95.2 mobile (both Android 11), it results in the behaviour described - an unfulfilled promise. and the appearance of it all just hanging. I.e. Chrome 96 on desktop accepts navigator.credential.create when the URL is https://mydomain.com:myport and the rpID is mydomain.com but Chrome 97 (Android 11) and Firefox 95.2 (Android 11) both fail with no error.

From looking at the spec, I believe Chrome desktop's behaviour is correct and it is incorrect behaviour from Chrome and Firefox mobile.

Obviously there is no issue in your code (thanks again for making me realise!) But do you have a view on this behaviour?

MasterKale commented 2 years ago

This works fine on Chrome 96 desktop. But on Chrome 97 mobile and Firefox 95.2 mobile (both Android 11), it results in the behaviour described - an unfulfilled promise. and the appearance of it all just hanging. I.e. Chrome 96 on desktop accepts navigator.credential.create when the URL is https://mydomain.com:myport and the rpID is mydomain.com but Chrome 97 (Android 11) and Firefox 95.2 (Android 11) both fail with no error.

Hmm, can you post an example of the options you're passing into startRegistration() (assuming you're using @simplewebauthn/browser on the front end)? I know that WebAuthn on Android can't support discoverable credentials (previously known as resident keys) so that's one restriction, and I thought there was one other but I can't quite remember...anyway if I can see the options you're generating that might help jog my memory some more.

thechrisps commented 2 years ago

Sure, these are the options that work on desktop and not on mobile. Interesting about the resident keys.... however the options here are the same whether its running on a custom port (doesn't work) or 443 (does), so I don't think that could be an issue.

And yes, using your browser and server pretty much out of the box through Docker, just with some very minor changes to load domain and port from .env

publickey:{
    "challenge": {},
    "rp": {
        "name": "SimpleWebAuthn Example",
        "id": "mydomain.com"
    },
    "user": {
        "id": {
            "0": 105,
            "1": 110,
            "2": 116,
            "3": 101,
            "4": 114,
            "5": 110,
            "6": 97,
            "7": 108,
            "8": 85,
            "9": 115,
            "10": 101,
            "11": 114,
            "12": 73,
            "13": 100
        },
        "name": "user@mydomain.com",
        "displayName": "user@mydomain.com"
    },
    "pubKeyCredParams": [
        {
            "alg": -7,
            "type": "public-key"
        },
        {
            "alg": -257,
            "type": "public-key"
        }
    ],
    "timeout": 60000,
    "attestation": "indirect",
    "excludeCredentials": [],
    "authenticatorSelection": {
        "userVerification": "preferred",
        "requireResidentKey": false
    }
}
MasterKale commented 2 years ago

Hmm, your challenge and user.id looks suspect. That's the object you're passing to startRegistration()?

If so you need to pass those values as strings, not raw ArrayBuffer's. Internally startRegistration() will decode the base64url-encoded challenge to a buffer, and user.id from whatever string you specify to a buffer as well (this one doesn't have to be base64url-encoded).

thechrisps commented 2 years ago

Apologies for the slow reply; hectic week. Should have said, that is a JSON.Stringify output of the object. I haven't changed any of the object creation from your code, so should be identical...

object

MasterKale commented 2 years ago

@thechrisps Sorry about the delay, I didn't mean to ignore you.

Can you try registering and authenticating on https://example.simplewebauthn.dev? I tried just now on my OG Pixel running Android 10 and Chrome 93 and was able to use my fingerprint sensor no problem for both; I'm curious if the site will work for you and your device.

We've already established yours is a hardware-specific issue. Perhaps you're running up against Android and its infamous lack of support for discoverable credentials...

MasterKale commented 2 years ago

I'm going to close this out for now due to inactivity, please feel free to reopen it if need be.