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.62k stars 137 forks source link

No passkey found #403

Closed igorschechtel closed 1 year ago

igorschechtel commented 1 year ago

Describe the issue

I am trying to implement passkey authentication with SimpleWebAuthn.

I am able to register a passkey successfully using Chrome on Ubuntu and my Galaxy S23+ phone. But when I try to authenticate I end up receiving "No passkey found for \<URL>" on my phone.

Reproduction Steps

1. Registration

image

image

I passed these options to startRegistration:

{
    "challenge": "ieMW8d53-B5RqV9fBf7ujeqsqMHjJM_0Zbyc8-GN7-8",
    "rp": {
        "name": "NachoNacho",
        "id": "app.nachonacho.co"
    },
    "user": {
        "id": "clj7fqdy200bsrnrw89711lta",
        "name": "igor lastNameSuperAdmin",
        "displayName": "igor lastNameSuperAdmin"
    },
    "pubKeyCredParams": [
        {
            "alg": -8,
            "type": "public-key"
        },
        {
            "alg": -7,
            "type": "public-key"
        },
        {
            "alg": -257,
            "type": "public-key"
        }
    ],
    "timeout": 120000,
    "attestation": "none",
    "excludeCredentials": [],
    "authenticatorSelection": {
        "residentKey": "preferred",
        "userVerification": "preferred",
        "requireResidentKey": false
    },
    "extensions": {
        "credProps": true
    }
}

And get this as response:

{
    "id": "nlh4fCZfadgLO097WUFJPQ",
    "rawId": "nlh4fCZfadgLO097WUFJPQ",
    "response": {
        "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUGVvljmuOFJnpG37rjEAW5eEqzYAcJ2fArz3sGmPv0cJdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJ5YeHwmX2nYCztPe1lBST2lAQIDJiABIVggPnG1ia_1ODQM8Spf_x7OyJLxP8BDCnv0uF8JMygDON0iWCC152ktI1QPVhPIq0x3PZ-cjSBgTdEJT8imzv8EaBOfDw",
        "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiaWVNVzhkNTMtQjVScVY5ZkJmN3VqZXFzcU1IakpNXzBaYnljOC1HTjctOCIsIm9yaWdpbiI6Imh0dHBzOi8vYXBwLm5hY2hvbmFjaG8uY28iLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
        "transports": [
            "hybrid",
            "internal"
        ]
    },
    "type": "public-key",
    "clientExtensionResults": {
        "credProps": {
            "rk": true
        }
    },
    "authenticatorAttachment": "cross-platform"
}

2. Authentication

Options passed to startAuthentication:

{
    "challenge": "G00Ov-3Do38Jx3NaI_k8Y1wr7Y0ommMtoz7inGXx9ag",
    "allowCredentials": [
        {
            "id": "bmxoNGZDWmZhZGdMTzA5N1dVRkpQUT09",
            "type": "public-key"
        }
    ],
    "timeout": 60000,
    "userVerification": "preferred"
}

image

Expected behavior

I expected a passkey to be found.

Dependencies

SimpleWebAuthn Libraries

$ npm list --depth=0 | grep @simplewebauthn
├── @simplewebauthn/browser@7.2.0
├── @simplewebauthn/server@7.3.0
# ...
MasterKale commented 1 year ago

Hello @igorschechtel, can you please include the response out of startRegistration() in the browser, and then the options you pass into startAuthentication() afterwards? I want to check if the credential ID out of registration matches up with what's included in allowCredentials during authentication.

igorschechtel commented 1 year ago

Thanks for the quick response @MasterKale! From your comment, I was able to figure out what was wrong (I was not encoding/decoding the credentialID correctly).

Just one remark: in the VerifiedRegistrationResponse the credential id is typed as Uint8Array, but I actually receive an object, as seen here (same for credentialPublicKey and attestationObject):

{
    "verified": true,
    "registrationInfo": {
        "fmt": "none",
        "counter": 0,
        "aaguid": "00000000-0000-0000-0000-000000000000",
        "credentialID": {
            "0": 196,
            "1": 81,
            "2": 27,
            "3": 162,
            "4": 107,
            "5": 43,
            "6": 168,
            "7": 233,
            "8": 85,
            "9": 231,
            "10": 157,
            "11": 23,
            "12": 68,
            "13": 160,
            "14": 230,
            "15": 202
        },
        "credentialPublicKey": {
            "0": 165,
            "1": 1,
            "2": 2,
            "3": 3,
            "4": 38,
            "5": 32,
            "6": 1,
            "7": 33,
            "8": 88,
            "9": 32,
            "10": 39,
            "11": 233,
            "12": 177,
            "13": 151,
            "14": 76,
            "15": 52,
            "16": 254,
            "17": 4,
            "18": 38,
            "19": 83,
            "20": 250,
            "21": 63,
            "22": 225,
            "23": 183,
            "24": 18,
            "25": 202,
            "26": 114,
            "27": 191,
            "28": 137,
            "29": 21,
            "30": 151,
            "31": 21,
            "32": 168,
            "33": 190,
            "34": 195,
            "35": 169,
            "36": 72,
            "37": 224,
            "38": 62,
            "39": 58,
            "40": 228,
            "41": 175,
            "42": 34,
            "43": 88,
            "44": 32,
            "45": 156,
            "46": 106,
            "47": 144,
            "48": 253,
            "49": 52,
            "50": 226,
            "51": 58,
            "52": 91,
            "53": 233,
            "54": 31,
            "55": 58,
            "56": 112,
            "57": 236,
            "58": 113,
            "59": 180,
            "60": 109,
            "61": 3,
            "62": 7,
            "63": 218,
            "64": 60,
            "65": 98,
            "66": 23,
            "67": 21,
            "68": 232,
            "69": 105,
            "70": 240,
            "71": 155,
            "72": 79,
            "73": 117,
            "74": 146,
            "75": 111,
            "76": 224,
            "dataView": {}
        },
        "credentialType": "public-key",
        "attestationObject": {
            "0": 163,
            "1": 99,
            "2": 102,
            "3": 109,
            "4": 116,
            "5": 100,
            "6": 110,
            "7": 111,
            "8": 110,
            "9": 101,
            "10": 103,
            "11": 97,
            "12": 116,
            "13": 116,
            "14": 83,
            "15": 116,
            "16": 109,
            "17": 116,
            "18": 160,
            "19": 104,
            "20": 97,
            "21": 117,
            "22": 116,
            "23": 104,
            "24": 68,
            "25": 97,
            "26": 116,
            "27": 97,
            "28": 88,
            "29": 148,
            "30": 25,
            "31": 91,
            "32": 229,
            "33": 142,
            "34": 107,
            "35": 142,
            "36": 20,
            "37": 153,
            "38": 233,
            "39": 27,
            "40": 126,
            "41": 235,
            "42": 140,
            "43": 64,
            "44": 22,
            "45": 229,
            "46": 225,
            "47": 42,
            "48": 205,
            "49": 128,
            "50": 28,
            "51": 39,
            "52": 103,
            "53": 192,
            "54": 175,
            "55": 61,
            "56": 236,
            "57": 26,
            "58": 99,
            "59": 239,
            "60": 209,
            "61": 194,
            "62": 93,
            "63": 0,
            "64": 0,
            "65": 0,
            "66": 0,
            "67": 0,
            "68": 0,
            "69": 0,
            "70": 0,
            "71": 0,
            "72": 0,
            "73": 0,
            "74": 0,
            "75": 0,
            "76": 0,
            "77": 0,
            "78": 0,
            "79": 0,
            "80": 0,
            "81": 0,
            "82": 0,
            "83": 0,
            "84": 16,
            "85": 196,
            "86": 81,
            "87": 27,
            "88": 162,
            "89": 107,
            "90": 43,
            "91": 168,
            "92": 233,
            "93": 85,
            "94": 231,
            "95": 157,
            "96": 23,
            "97": 68,
            "98": 160,
            "99": 230,
            "100": 202,
            "101": 165,
            "102": 1,
            "103": 2,
            "104": 3,
            "105": 38,
            "106": 32,
            "107": 1,
            "108": 33,
            "109": 88,
            "110": 32,
            "111": 39,
            "112": 233,
            "113": 177,
            "114": 151,
            "115": 76,
            "116": 52,
            "117": 254,
            "118": 4,
            "119": 38,
            "120": 83,
            "121": 250,
            "122": 63,
            "123": 225,
            "124": 183,
            "125": 18,
            "126": 202,
            "127": 114,
            "128": 191,
            "129": 137,
            "130": 21,
            "131": 151,
            "132": 21,
            "133": 168,
            "134": 190,
            "135": 195,
            "136": 169,
            "137": 72,
            "138": 224,
            "139": 62,
            "140": 58,
            "141": 228,
            "142": 175,
            "143": 34,
            "144": 88,
            "145": 32,
            "146": 156,
            "147": 106,
            "148": 144,
            "149": 253,
            "150": 52,
            "151": 226,
            "152": 58,
            "153": 91,
            "154": 233,
            "155": 31,
            "156": 58,
            "157": 112,
            "158": 236,
            "159": 113,
            "160": 180,
            "161": 109,
            "162": 3,
            "163": 7,
            "164": 218,
            "165": 60,
            "166": 98,
            "167": 23,
            "168": 21,
            "169": 232,
            "170": 105,
            "171": 240,
            "172": 155,
            "173": 79,
            "174": 117,
            "175": 146,
            "176": 111,
            "177": 224,
            "dataView": {}
        },
        "userVerified": true,
        "credentialDeviceType": "multiDevice",
        "credentialBackedUp": true
    }
}

Is that expected? I had to manually convert the object to an array to then encode to base64url.

MasterKale commented 1 year ago

You fed the output from verifyRegistrationResponse() into JSON.stringify(), didn't you? 😂

The main reason I wrote SimpleWebAuthn is because using WebAuthn requires you to know that Uint8Array's don't stringify nicely at all - when you try you get crazy output like in your code block above. It's better to encode and decode them into a more JSON-friendly structure like a string which was the primary reason for my requesting the output from startRegistration(). This method in @simplewebauthn/browser encodes those byte arrays into base64url strings, which makes for cleaner responses to share with others as JSON.

In this case it's fine, we don't need to spend any more time on this because you figured out your problem was related to how credential IDs were getting passed back to the front end during authentication.

Thanks for the quick response @MasterKale! From your comment, I was able to figure out what was wrong (I was not encoding/decoding the credentialID correctly).

Closing this out for now.

igorschechtel commented 1 year ago

Thanks @MasterKale! Indeed I was using JSON.stringify() when passing the response of startRegistration() from the front end to the back end.

One last thing: am I able to use SimpleWebAuthn in different subdomains (e.g. register from app.nachonacho.co and authenticate from connect.nachonacho.co)? I tried setting the rp id to nachonacho.co and setting the expectedOrigin to an array with both subdomains, but it didn't work...

MasterKale commented 1 year ago

One last thing: am I able to use SimpleWebAuthn in different subdomains (e.g. register from app.nachonacho.co and authenticate from connect.nachonacho.co)? I tried setting the rp id to nachonacho.co and setting the expectedOrigin to an array with both subdomains, but it didn't work...

This is a good question, but since it's not directly related to this issue, can you ask this again but as a discussion instead? It feels like the kind of question with an answer that'll benefit others in the future.