Coder Social home page Coder Social logo

passwordless-id / webauthn Goto Github PK

View Code? Open in Web Editor NEW
407.0 8.0 47.0 1.77 MB

Webauthn / passkeys helper library to make your life easier. Client side, server side and demo included.

Home Page: https://passwordless-id.github.io/webauthn

License: MIT License

TypeScript 87.97% JavaScript 0.58% Python 11.45%
authentication passkeys passwordless webauthn

webauthn's Introduction

Passwordless.ID / webauthn

A greatly simplified and opinionated wrapper to invoke the webauthn protocol more conveniently. It is an open source, dependency-free and minimalistic library (17kb only, from which 11kb is the list of authenticator aaguids/names).

This library is used in Passwordless.ID, a free public identity provider based on WebAuthn.

Try out the playground to see how this library works:

Other demos with minial examples:

The source of all demos is on GitHub

How does the protocol work?

This diagram shows how the webauthn protocol works, slightly simplified.

diagram

Further documentation about the protocol can be found in the webauthn guide at Passwordless.ID.

Installation / Usage

NPM

npm install @passwordless-id/webauthn
import * as webauthn from '@passwordless-id/webauthn'

Browser

<script type="module">
  import { client } from 'https://unpkg.com/@passwordless-id/[email protected]/dist/webauthn.min.js'
</script>

Import

The webauthn module is basically a "bundle" composed of the following modules:

  • client: used for invoking webauthn in the browser
  • server: used for verifying responses in the server
  • parsers: used to parse part or all of the encoded data without verifications
  • utils: various encoding, decoding, challenge generator and other utils

It was designed that way so that you can import only the module(s) you need. That way, the size of your final js bundle is reduced even further. Importing all is dependency free and < 10kb anyway.

So you might for example import { client } from '@passwordless-id/webauthn' for browser side stuff and import { server } from '@passwordless-id/webauthn' for server side stuff.

Runs in...

  • In Chrome, Edge, Firefox, Safari
  • NodeJS 19+ (*)
  • Cloudflare Workers
  • Probably in most recent browsers/servers

(*) For older Node versions, take a look at Arch0125's fork. (The reason of the Node 19+ compatibility is basically WebCrypto being globally available, making it possible to use the same build for all targets: browser, node, clouflare workers...)

Utilities

import { client } from '@passwordless-id/webauthn' 

client.isAvailable()

Returns true or false depending on whether the Webauthn protocol is available on this platform/browser. Particularly linux and "exotic" web browsers might not have support yet.


await client.isLocalAuthenticator()

This promise returns true or false depending on whether the device itself can act as authenticator. Otherwise, a "roaming" authenticator like a smartphone or usb security key can be used. This information is mainly used for information messages and user guidance.

Registration

Overview

The registration process occurs in four steps:

  1. The browser requests a challenge from the server
  2. The browser triggers client.register(...) and sends the result to the server
  3. The server parses and verifies the registration payload
  4. The server stores the credential key of this device for the user account

Note that unlike traditional authentication, the credential key is attached to the device. Therefore, it might make sense for a single user account to have multiple credential keys.

1. Requesting challenge

The challenge is basically a nonce to avoid replay attacks.

const challenge = /* request it from server */

Remember it on the server side during a certain amount of time and "consume" it once used.

2. Trigger registration in browser

Example call:

import { client } from '@passwordless-id/webauthn' 

const challenge = "a7c61ef9-dc23-4806-b486-2428938a547e"
const registration = await client.register("Arnaud", challenge, {
  authenticatorType: "auto",
  userVerification: "required",
  timeout: 60000,
  attestation: true,
  userHandle: "Optional server-side user id. Must not reveal personal information.",
  debug: false
})

Parameters:

  • username: The desired username.
  • challenge: A server-side randomly generated string.
  • options: See below.

The registration object looks like this:

{
  "username": "Arnaud",
  "credential": {
    "id": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
    "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
    "algorithm": "ES256"
  },
  "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAiYcFjK3EuBtuEw3lDcvpYAIN_duB4SXSTMv7L51KME_HqF6zjjujSz_EivOatkT8XVpQECAyYgASFYIIMmKkJlAJg5_Se3UecZfh5cgANEdl1ebIEEZ0hl2y7fIlgg8QqxHQ9SFb75Mk5kQ9esvadwtjuD02dDhf2WA9iYE1Q=",
  "clientData": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYTdjNjFlZjktZGMyMy00ODA2LWI0ODYtMjQyODkzOGE1NDdlIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="
}

Then simply send this object as JSON to the server.

3. Verify it server side

import { server } from '@passwordless-id/webauthn' 

const expected = {
    challenge: "a7c61ef9-dc23-4806-b486-2428938a547e", // whatever was randomly generated by the server
    origin: "http://localhost:8080",
}
const registrationParsed = await server.verifyRegistration(registration, expected)

Either this operation fails and throws an Error, or the verification is successful and returns the parsed registration. Example result:

{
  "username": "Arnaud",
  "credential": {
    "id": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
    "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
    "algorithm": "ES256"
  },
  "authenticator": {
    ...
    "name": "Windows Hello",
    "icon_dark": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-dark.png",
    "icon_light": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-light.png",
    "synced": true
  },
  ...
}

NOTE: Currently, the attestation which proves the exact model type of the authenticator is not verified. Do I need attestation?

4. Store the credential key

The credential key is the most important part and should be stored in a database for later since it will be used to verify the authentication signature.

"credential": {
  "id": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
  "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
  "algorithm": "ES256"
},

Please note that unlike traditional systems, you might allow a user to have multiple credential keys. For example, if you allow the user to use multiple device-bound keys and/or registering keys for multiple platforms.

Authentication

Overview

There are two kinds of authentications possible:

  • by providing a list of allowed credential IDs
  • by letting the platform offer a default UI to select the user and its credential

Both have their pros & cons (TODO: article).

The authentication procedure is similar to the procedure and divided in four steps.

  1. The browser requests a challenge from the server
  2. The browser triggers client.authenticate(...) and sends the result to the server
  3. The server loads the credential key used for authentication
  4. The server parses and verifies the authentication payload

1. Requesting challenge

The challenge is basically a nonce to avoid replay attacks.

const challenge = /* request it from server */

Remember it on the server side during a certain amount of time and "consume" it once used.

2. Trigger authentication in browser

Example call:

import { client } from '@passwordless-id/webauthn'

const challenge = "56535b13-5d93-4194-a282-f234c1c24500"
const authentication = await client.authenticate(["3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU"], challenge, {
  "authenticatorType": "auto",
  "userVerification": "required",
  "timeout": 60000
})

Example response:

{
  "credentialId": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
  "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ==",
  "clientData": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiNTY1MzViMTMtNWQ5My00MTk0LWEyODItZjIzNGMxYzI0NTAwIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0=",
  "signature": "MEUCIAqtFVRrn7q9HvJCAsOhE3oKJ-Hb4ISfjABu4lH70MKSAiEA666slmop_oCbmNZdc-QemTv2Rq4g_D7UvIhWT_vVp8M="
}

Parameters:

  • credentialIds: The list of credential IDs that can be used for signing.
  • challenge: A server-side randomly generated string, the base64url encoded version will be signed.
  • options: See below.

3. In the server, load the credential key

import { server } from '@passwordless-id/webauthn' 

const credentialKey = { // obtained from database by looking up `authentication.credentialId`
    id: "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
    publicKey: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
    algorithm: "ES256"
} as const

const expected = {
    challenge: "56535b13-5d93-4194-a282-f234c1c24500", // whatever was randomly generated by the server.
    origin: "http://localhost:8080",
    userVerified: true, // should be set if `userVerification` was set to `required` in the authentication options (default)
    counter: 123 // Optional. For device-bound credentials, you should verify the authenticator "usage" counter increased since last time.
}

Regarding the counter, it might or might not be implemented by the authenticator. Typically, it's implemented by hardware-bound keys to detect and avoid the risk of cloning the authenticator and starts with 1 during registration. On the opposite, for password managers syncing keys in the cloud, the counter is typically always 0 since in that case cloning is a "feature". For example, device-bound keys on Android and Windows do have an increasing counter, USB security keys also, while MacOS/iOS do not. Lastly, please note that the specs do not mandate "+1" increases, it could theoretically increase by any amount.

Often, it might also be more practical to use functions to verify challenge or origin. This is possible too:

const expected = {
    challenge: async (challenge) => { /* async call to DB for example */ return true },
    origin: (origin) => listOfAllowedOrigins.includes(origin),
    userVerified: true, // no function allowed here
    counter: 123,  // optional, no function allowed here
    verbose: true, // optional, enables debug logs containing sensitive information
}

4. Verify the authentication

const authenticationParsed = await server.verifyAuthentication(authentication, credentialKey, expected)

Either this operation fails and throws an Error, or the verification is successful and returns the parsed authentication payload.

Please note that this parsed result authenticationParsed has no real use. It is solely returned for the sake of completeness. The verifyAuthentication already verifies the payload, including the signature.

Remarks

The challenge is critical

The challenge must be a random value. Otherwise, your implementation might become vulnerable to replay attacks.

There can be multiple credentials per user ID

Unlike traditional authentication, you might have multiple credential keys per user.

Authentication does not provide username out of the box

Only credentialId is provided during the authentication.

So either you maintain a mapping credentialId -> username in your database, or you add the username in your frontend to backend communication.

Passkeys a.k.a "discoverable" credentials

If the credential is discoverable, the credential id and user information is kept on the system. Although you can "discourage" it, there is no option in the spec to "forbid" it. For example, iOS always use "discoverable" keys, even if the option is set to "discouraged"

Afterwards, you can use client.authenticate([], ...) without specifying credential IDs. In that case, the platform will pop-up a default dialog to let you pick a user and perform authentication. Of course, the look and feel is platform specific.

Disable synced credentials

That is sadly impossible, the spec authors refuse to add an option to disable syncing.

See:

Note that in practice, and although not precised by the specs, the "discoverable" property has an impact on syncing in the cloud. As of end 2023, for Microsoft and Android, when marked as discoverable: "discouraged", they are not synced in the cloud ...but still discoverable. Since this is not covered by the specs, this might change in the future.

This library simplifies a few things by using sensible defaults

Unlike the WebAuthn protocol, some defaults are different:

  • The timeout is one minute by default.
  • If the device can act as authenticator itself, it is preferred instead of asking which authenticator type to use.
  • The userVerification is required by default.
  • The protocol "Relying Party ID" is always set to be the origin domain
  • The username is used for both the protocol level user "name" and "displayName"

Common options

The following options are available for both register and authenticate.

  • timeout: Number of milliseconds the user has to respond to the biometric/PIN check. (Default: 60000)
  • userVerification: Whether to prompt for biometric/PIN check or not. (Default: "required")
  • authenticatorType: Which device to use as authenticator. Possible values:
    • 'auto': if the local device can be used as authenticator it will be preferred. Otherwise it will prompt for a roaming device. (Default)
    • 'local': use the local device (using TouchID, FaceID, Windows Hello or PIN)
    • 'roaming': use a roaming device (security key or connected phone)
    • 'both': prompt the user to choose between local or roaming device. The UI and user interaction in this case is platform specific.
  • domain: by default, the current domain name is used. Also known as "relying party id". You may want to customize it for ...
    • a parent domain to let the credential work on all subdomains
    • browser extensions requiring specific IDs instead of domains ?
    • specific iframes use cases?
  • debug: If enabled, parses the "data" objects and provide it in a "debug" properties.

Registration options

  • discoverable: ('discouraged', 'preferred' or 'required') If the credential is "discoverable", it can be selected using authenticate without providing credential IDs. In that case, a native pop-up will appear for user selection. This may have an impact on the "passkeys" user experience and syncing behavior of the key. (Default: 'preferred')
  • attestation: If enabled, the device attestation and clientData will be provided as base64 encoded binary data. Note that this may impact the authenticator information available or the UX depending on the platform. (Default: false)
  • userHandle: The user "handle" (also known as user "id") can be used to re-register credentials for an existing user, thus overriding the current credential key pair and username for that userHandle. The default here is based on a hash of the username, and thus has some security implications as described in issue.

Authentication options

Verification options

  • userVerified: to ensure that the user has been verified by the authenticator
  • counter: this should be an incrementing value on each authentication, but it was made optional according to #38
  • domain: in case you used a specific domain (relying party id) during registration/authentication, you need this too during verification
  • verbose: prints more details to the console if enabled

Parsing data

If you want to parse the encoded registration, authentication or parts of it without verifying it, it is possible using the parsers module. This might be helpful when debugging.

Registration

import { parsers } from '@passwordless-id/webauthn'

parsers.parseRegistration({
      "username": "Arnaud",
      "credential": {
        "id": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
        "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
        "algorithm": "ES256"
      },
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAiYcFjK3EuBtuEw3lDcvpYAIN_duB4SXSTMv7L51KME_HqF6zjjujSz_EivOatkT8XVpQECAyYgASFYIIMmKkJlAJg5_Se3UecZfh5cgANEdl1ebIEEZ0hl2y7fIlgg8QqxHQ9SFb75Mk5kQ9esvadwtjuD02dDhf2WA9iYE1Q=",
      "clientData": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYTdjNjFlZjktZGMyMy00ODA2LWI0ODYtMjQyODkzOGE1NDdlIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="
    })
{
  "username": "Arnaud",
  "credential": {
    "id": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
    "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgyYqQmUAmDn9J7dR5xl-HlyAA0R2XV5sgQRnSGXbLt_xCrEdD1IVvvkyTmRD16y9p3C2O4PTZ0OF_ZYD2JgTVA==",
    "algorithm": "ES256"
  },
  "client": {
    "type": "webauthn.create",
    "challenge": "a7c61ef9-dc23-4806-b486-2428938a547e",
    "origin": "http://localhost:8080",
    "crossOrigin": false
  },
  "authenticator": {
    "rpIdHash": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2M=",
    "flags": {
      "userPresent": true,
      "userVerified": true,
      "backupEligibility": false,
      "backupState": false,
      "attestedData": true,
      "extensionsIncluded": false
    },
    "counter": 0,
    "aaguid": "08987058-cadc-4b81-b6e1-30de50dcbe96",
    "name": "Windows Hello",
    "icon_dark": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-dark.png",
    "icon_light": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-light.png",
    "synced": true
  },
  "attestation": null
}

Authentication

import { parsers } from '@passwordless-id/webauthn'

parsers.parseAuthentication({
      "credentialId": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ==",
      "clientData": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiNTY1MzViMTMtNWQ5My00MTk0LWEyODItZjIzNGMxYzI0NTAwIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0=",
      "signature": "MEUCIAqtFVRrn7q9HvJCAsOhE3oKJ-Hb4ISfjABu4lH70MKSAiEA666slmop_oCbmNZdc-QemTv2Rq4g_D7UvIhWT_vVp8M="
    })
{
  "credentialId": "3924HhJdJMy_svnUowT8eoXrOOO6NLP8SK85q2RPxdU",
  "client": {
    "type": "webauthn.get",
    "challenge": "56535b13-5d93-4194-a282-f234c1c24500",
    "origin": "http://localhost:8080",
    "crossOrigin": false,
    "other_keys_can_be_added_here": "do not compare clientDataJSON against a template. See https://goo.gl/yabPex"
  },
  "authenticator": {
    "rpIdHash": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2M=",
    "flags": {
      "userPresent": true,
      "userVerified": true,
      "backupEligibility": false,
      "backupState": false,
      "attestedData": false,
      "extensionsIncluded": false
    },
    "counter": 1
  },
  "signature": "MEUCIAqtFVRrn7q9HvJCAsOhE3oKJ-Hb4ISfjABu4lH70MKSAiEA666slmop_oCbmNZdc-QemTv2Rq4g_D7UvIhWT_vVp8M="
}

clientData

import { parsers } from '@passwordless-id/webauthn'

parsers.parseClient("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYTdjNjFlZjktZGMyMy00ODA2LWI0ODYtMjQyODkzOGE1NDdlIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ==")
{
    "type": "webauthn.create",
    "challenge": "a7c61ef9-dc23-4806-b486-2428938a547e",
    "origin": "http://localhost:8080",
    "crossOrigin": false
  }

authenticatorData

import { parsers } from '@passwordless-id/webauthn'

parsers.parseAuthenticator("SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAiYcFjK3EuBtuEw3lDcvpYAIN_duB4SXSTMv7L51KME_HqF6zjjujSz_EivOatkT8XVpQECAyYgASFYIIMmKkJlAJg5_Se3UecZfh5cgANEdl1ebIEEZ0hl2y7fIlgg8QqxHQ9SFb75Mk5kQ9esvadwtjuD02dDhf2WA9iYE1Q=")
{
    "rpIdHash": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2M=",
    "flags": {
      "userPresent": true,
      "userVerified": true,
      "backupEligibility": false,
      "backupState": false,
      "attestedData": true,
      "extensionsIncluded": false
    },
    "counter": 0,
    "aaguid": "08987058-cadc-4b81-b6e1-30de50dcbe96",
    "name": "Windows Hello",
    "icon_dark": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-dark.png",
    "icon_light": "https://webauthn.passwordless.id/authenticators/08987058-cadc-4b81-b6e1-30de50dcbe96-light.png",
    "synced": true
  }

Please note that aaguid and name are only available during registration.

What is the difference between this and Passwordless.ID?

This library is a wrapper around the WebAuthn protocol. It is the technical foundation for strong authentication. No more, no less.

Passwordless.ID is a service. It provides is all the other things required for a complete authentication system:

  • multiple registered devices per account
  • user profile
  • e-mail verification (phone should come too at some point)
  • account recovery mechanisms
  • OAuth2/OpenID integration
  • ...

This WebAuthn library enables you to build a custom solution from scratch. In contrast, Passwordless.ID enables you to use a "Sign in with Passwordless.ID" button, a bit like "Sign in with Google/Microsoft/Apple" but platform neutral, secure and without configuration.

webauthn's People

Contributors

asgarovf avatar boennemann avatar dagnelies avatar functino avatar johnnycrazy avatar kevinsimper avatar mattmazzola avatar rersozlu avatar saknarak avatar vibinoschi avatar wootch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webauthn's Issues

Error: require() of ES Module - Instead change the require of index.js in applicationMutation.ts to a dynamic import() which is available in all CommonJS modules.

Hello,

When in the backend (Express) with Typescript,
I do this

npm install @passwordless-id/webauthn

then:

import { server } from '@passwordless-id/webauthn'
const applicationMutation = {
  createPasskeyApplication: async (_parent: unknown, args: Args, ctx: Ctx): Promise<Application> => {
     const registrationParsed = await server.verifyRegistration(args, expected)
  }
}

When I compile, I have this error:

[ERROR] 18:29:27 Error: require() of ES Module /Users/alan/Documents/app/backend/server/graphql/node_modules/@passwordless-id/webauthn/dist/esm/index.js from /Users/alan/Documents/app/backend/server/graphql/src/resolvers/mutation/applicationMutation.ts not supported.
Instead change the require of index.js in /Users/alan/Documents/app/backend/server/graphql/src/resolvers/mutation/applicationMutation.ts to a dynamic import() which is available in all CommonJS modules.
(node:22269) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.

Any idea where this issue might come from?
Thanks fro your help!

Types are not included in 0.0.12 package

It seems the package was published without the types being generated so the package.types is referencing a non existing locastion

image

When looking at 0.0.12 package, you can see the dist/types is not included

image

Related to #6

Also dist/esm is not included although that isn't as large of problem, given the minified has same functionality.

Import issue in Nestjs

Error

const webauthn = require("@passwordless-id/webauthn");
                 ^
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/neerajsingh/work/Deinfra/barter-api/node_modules/@passwordless-id/webauthn/dist/esm/index.js from /**/dist/authentication/authentication.service.js not supported.
Instead change the require of index.js in /**/dist/authentication/authentication.service.js to a dynamic import() which is available in all CommonJS modules.

Code

import * as webauthn from '@passwordless-id/webauthn';

// OR 

import { server } from '@passwordless-id/webauthn';

// both doesn't work and give same error

tsconfig

{
  "compilerOptions": {
    "module": "commonjs",
    }
 }

// can't change this to ES6 which result into issue for all other imports

Regenerate issue

install nestjs
install '@passwordless-id/webauthn';
try to import and run

Counters don't work on macbook

It appears that macbooks only return a 0

image

Assertion verification error: Error: Unexpected authenticator counter: 0 (should be > 1)
    at Module.verifyAuthentication (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]/node_modules/@passwordless-id/webauthn/dist/esm/server.js:51:73)
    at async POST (webpack-internal:///(rsc)/./app/api/authorize/route.ts:59:30)
    at async /Users/devinelliot/_integration_tests/iron-account/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:62609

I was able to nail this down to a point where I can confirm that only 0 is ever returned. If you set the initial counter to -1 as the documentation describes then you will only get a valid auth one time. The live demo on the site is also failing in the same way.

Can the counter be set as optional as it appears passkey expect no counter at all? I'm still reading into this

Error registering key using 1Password

Hello,
There seems to be something odd happening when trying to register using 1Password Passkeys.
I see that a similar issue was reported in #37, but according to 1Password support, getPublicKey has been implemented since the 1Password browser extension version 2.16.1-2 stable.
The error message in #37 also differs from mine:

Error: Permission denied to access object
    E utils.ts:15
    o utils.ts:24
    v client.ts:119
    register playground.js:50
    VueJS 4
        click
        dn
        r
        _wrapper
playground.js:66:25

I have tested on both my page using @passwordless-id/webauthn and on the playground demo.

Any idea on what could be wrong and if the bug is with 1Password or this project?

Suggestion: Use squash for merging for PRs

I noticed the recent PRs that I created were merged using the default strategy which results in more complicated git history.

image

These were very small PRs so it's minor, although I would suggest using the "squash" merge strategy to keep the history of main linear. I wasn't sure if you were aware that you can go to the repo settings and make it the only allowed option so that there isn't a chance to pick the wrong one.

image

Feel free to close issue or ignore if you'd rather not.

Display QR code immediatly

For some reason when I make a registration or login func, I get multiple popup windows appearing. First it asks me to always use USB key, no matter which options I will pass. If I press cancel then it shows another window to choose if I want to use tablet, phone etc and only when you select it displays qr code.

Is there any way to display that qr code as soon as the user presses the button? We know that the users will be using mobile phones always so these window popups are worthless for us and very confusing.

Using 1Password does not work on demo

Trying the demo at https://webauthn.passwordless.id/demos/basic.html, I found that using 1Password to save my passkey doesn't work. 1P will save the passkey, but the client can't unpack the response to send it to the server.

Here's a demo of what I mean:

Screen.Recording.2023-08-18.at.16.43.28-2.mp4

I found this out by using the webauthn client in my own project and confirmed it in the basic demo. In my project, I saw errors that getPublicKey() didn't exist. I believe that is optional, so it's not too surprising.

Do you have any suggestions on what can be done to support 1Password? I can look at making changes, but I don't know the Webauthn spec that well. I know your project is focused, so you may not want to support these kinds of authenticators at all.

Using both my browser and a remote authenticator (my phone) did work. It's just 1Password and it's weird responses that doesn't.

This the credential created by 1Password:

image

and the response:

image

the clientDataJSON:

{
    "type": "webauthn.create",
    "challenge": "5146a02d-2124-4c50-93af-d64a9ff8ce70",
    "origin": "<snip>"
}

Question about addition of credential.getClientExtensionResults() on registration object

Related to #14, I as I was looking at the available data and which pieces of information I could use as user id. I came across:
PublicKeyCredential/getClientExtensionResults

I wondered if it could be used here to supply "extra" data to the returned object. I'm not very familiar with these APIs so perhaps it is not useful, but I thought it was worth asking. Can close if it's not applicable.

webauthn/src/client.ts

Lines 110 to 119 in f6e63f0

let registration :RegistrationEncoded = {
username: username,
credential: {
id: credential.id,
publicKey: utils.toBase64url(response.getPublicKey()),
algorithm: getAlgoName(credential.response.getPublicKeyAlgorithm())
},
authenticatorData: utils.toBase64url(response.getAuthenticatorData()),
clientData: utils.toBase64url(response.clientDataJSON),
}

verifyAuthentication ERR_OSSL_EVP_DECODE_ERROR

Code๏ผš

        var challenge = passkey_challenge_store[timestamp];
        var credentialKey = {
		id: passkey.id,
		publicKey: passkey.publicKey,
		algorithm: passkey.algorithm,
	}

	var expected = {
		challenge,
		origin: get_option('nv_home_url'),
		userVerified: true,
	}

	console.log(authentication)
	console.log(credentialId)
	console.log(credentialKey)
	console.log(expected)

	return webAuthnServer.verifyAuthentication(authentication, credentialKey, expected);

Console๏ผš

{
  credentialId: 'pADVavwUiRWK_Rf_9ZZEOuZBaogKqJB-pgTIO24Pnxs',
  authenticatorData: 'SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAACQ==',
  clientData: 'eyJjaGFsbGVuZ2UiOiJjMjkxZDJjNS0xMmYxLTQxZTEtODlmMC0zZWQ3MWU3ZWM1ZjUiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=',
  signature: 'tRy_rJqHMfiow8nL90BRn8R4aZKXuWmh-xgllLkKiCR2HM8w9-975_tP552zJf-3ygRlbnu3H321vELGDosiSF6MiaDbp5jRGFlUXAW5PcikksdbJCYjao7O5e1zD7dZEQyrxW_Tu8blggwXZ0WvV2jDebEyDiF_Q6QC5cp-qCVLwskGS-_Qlyd4guLWNuEXsJFSvlBAfBC7390Fn5QaJS85EqQ7pO7191QIkoLZGCvPekdKVezzpXjotM7srSn4RGUJQBX6N8B3DAvq0FRa_-pze9k6UF7V13nyJIQPcW-60YbdmPMFpbagjsnTHBRI1RwAsYvKmEu3EMdoZPZ6UQ==',
  userHandle: 'lOXeWfRRCm1598JCasYucIj5F1aL24FwpCtyP5eO83o='
}
pADVavwUiRWK_Rf_9ZZEOuZBaogKqJB-pgTIO24Pnxs
{
  id: 'pADVavwUiRWK_Rf_9ZZEOuZBaogKqJB-pgTIO24Pnxs',
  publicKey: 'MIIBIjANBgkqhkiG9w0BAQsFAAOCAQ8AMIIBCgKCAQEA2JrQ5S5fV3rOc0AmKg7ljFMMcmnA62tjxGl-UVQVo77hsMwPfQjAM6fM3zpdLMWVGT7DN3AyErA2xIIDTcgcLFV0jp0RKwlnNm1uBYXkMlu9qI8XEioeyrwErYbkTKNOvjg4iXL3UU2bDxwUxCFt9ReihjaInfyo3aBjzC22P_fzQf0CUnw6Vo0SeuFdRTX5f1ZQY-56sMnPMyNqB4CBN-ejo9POG8k5nDh0IvaT2x7yfDz_YsB1Smhpj67Cb-Dl-NbqvFjikeYTzeOITBOGCaElJfExupww1DzUY-N9XN2yhph_XP9LvUH6qxc7N_BcoOtfpsDDYrDJ_hyvG6aaOQIDAQAB',
  algorithm: 'RS256'
}
{
  challenge: 'c291d2c5-12f1-41e1-89f0-3ed71e7ec5f5',
  origin: 'http://localhost:3000',
  userVerified: true
}
DOMException [DataError]: Invalid keyData
    at new DOMException (node:internal/per_context/domexception:53:5)
    at __node_internal_ (node:internal/util:695:10)
    at Object.rsaImportKey (node:internal/crypto/rsa:219:15)
    at SubtleCrypto.importKey (node:internal/crypto/webcrypto:615:10)
    ... 5 lines matching cause stack trace ...
    at new Promise (<anonymous>) {
  [cause]: Error: error:03000072:digital envelope routines::decode error
      at createPublicKey (node:internal/crypto/keys:619:12)
      at Object.rsaImportKey (node:internal/crypto/rsa:213:21)
      at SubtleCrypto.importKey (node:internal/crypto/webcrypto:615:10)
      at parseCryptoKey (file:///D:/SynologyDrive/Design/Html/nvPress/node_modules/@passwordless-id/webauthn/dist/esm/server.js:92:26)
      at verifySignature (file:///D:/SynologyDrive/Design/Html/nvPress/node_modules/@passwordless-id/webauthn/dist/esm/server.js:107:27)
      at Module.verifyAuthentication (file:///D:/SynologyDrive/Design/Html/nvPress/node_modules/@passwordless-id/webauthn/dist/esm/server.js:31:36)
      at global.async_verify_passkey_login (D:\SynologyDrive\Design\Html\nvPress\nv-includes\method-users.js:306:24)
      at D:\SynologyDrive\Design\Html\nvPress\nv-includes\init_default_users_api.js:163:4
      at new Promise (<anonymous>)
      at callback (D:\SynologyDrive\Design\Html\nvPress\nv-includes\init_default_users_api.js:162:10) {
    library: 'digital envelope routines',
    reason: 'decode error',
    code: 'ERR_OSSL_EVP_DECODE_ERROR'
  }
}

Question about exposing the ID created for the user in returned response object

I was wondering which value I should be persisting in the database for the user id.

I think the correct value is base64 encoded hash of the user name here:

id: await utils.sha256(new TextEncoder().encode(username)), // ID should not be directly "identifiable" for privacy concerns

However, I don't think this value is returned in the registration object on either the authenticatorData or clientData.
From what I understand the credential id isn't stable and shouldn't be used here.

I was thinking something like this:

const userId = await utils.sha256(new TextEncoder().encode(username))
const creationOptions: PublicKeyCredentialCreationOptions = {
        challenge: utils.parseBase64url(challenge),
        ...
        user: {
            id: usreId,
        ...
}

const registration: RegistrationEncoded = {
        username,
        userId,
        ...
}

Do you agree this user id should be returned?
If not, what should I be using for the user id?

Client: undefined is not a function

Using passwordless-id/webauthn on the react native, however facing some problems.
Fist was TextDecoder which I have fixed, but still there is one issue idk why:

import client from '@passwordless-id/webauthn';
const CreateCredentials = () => {
  const {loggedInUser} = useContext(UserLoggedInData);
  const isClientAvailable = client.isAvailable();
  //fetch data
  const credential = AsyncStorage.getItem('credential_');
  const challengeS = AsyncStorage.getItem('challenge_');

  // Registration

  const [isRegistered, setIsRegistered] = useState(false);
  const challenge =
    AsyncStorage.getItem('challenge_' + loggedInUser.username) || uuidv4();

  const checkIsRegistered = useCallback(async () => {
    setIsRegistered(
      !!AsyncStorage.getItem('credential_' + loggedInUser.username),
    );
  }, []);

  useEffect(() => {
    if (loggedInUser.username) {
      checkIsRegistered();
    }
  }, []);

  const register = useCallback(async () => {

    const res = await client.register(loggedInUser.username, challenge, {
      authenticatorType: 'auto',
      userVerification: 'required',
      timeout: 60000,
      attestation: false,
      debug: false,
    });
    console.log('====================================');
    console.log(res);
    console.log('====================================');
    AsyncStorage.setItem('username', loggedInUser.username);
    AsyncStorage.setItem(
      'credential_' + loggedInUser.username,
      parsed.credential.id,
    );
    AsyncStorage.setItem('challenge_' + loggedInUser.username, challenge);
    checkIsRegistered();
  }, []);
 ERROR  TypeError: undefined is not a function

This error is located at:
    in CreateCredentials

image

Error: Unexpected authenticator counter: 0 (should be > 0)

When using the playground both in Chrome on Desktop and Mobile (iOS):

  • I can Register Device succesfully with the default settings
  • When I press Login the client side details are filled up
  • But a toaster shows up with this error Error: Unexpected authenticator counter: 0 (should be > 0) and the second server side area shows:
Resulting into:
...

unable to use server on node.js

When use in browser there is no problem to use client
But when I try to use in node.js, client was not found

What should I do to skip import client that I never use

both node.js 18 and 19
step to reproduce

  1. npm init -y
  2. npm install @passwordless-id/webauthn
  3. change package.json add type: 'module'
  4. create index.js
import { server } from '@passwordless-id/webauthn'
console.log(server)
  1. run node index.js
  2. error occurs
node:internal/errors:490
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.\node_modules\@passwordless-id\webauthn\dist\esm\client' imported from .\node_modules\@passwordless-id\webauthn\dist\esm\index.js
    at new NodeError (node:internal/errors:399:5)
    at finalizeResolution (node:internal/modules/esm/resolve:231:11)
    at moduleResolve (node:internal/modules/esm/resolve:850:10)
    at defaultResolve (node:internal/modules/esm/resolve:1058:11)
    at nextResolve (node:internal/modules/esm/hooks:654:28)
    at Hooks.resolve (node:internal/modules/esm/hooks:309:30)
    at ESMLoader.resolve (node:internal/modules/esm/loader:312:26)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:172:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:76:40)
    at link (node:internal/modules/esm/module_job:75:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v19.8.1

Upcoming changes for version "2"?

There are a few things I would like to change in a version "2".
It's not really big fundamental changes, but they are "breaking" changes nonetheless requiring a major version bump.

  • Make register and authenticate functions accept a single object containing all properties directly instead of "(name/creds, challenge, options)"
  • The authenticator.synced property will be moved to credential.synced. I find it belongs there rather than in the authenticator, it's more intuitive/logic.
  • The userHandle property during registration will be removed. Instead you will be able to set user as either a string or as {id: ..., name: ..., displayName: ...} like in the original protocol.
  • The userHandle from the authentication result will be renamed userId
  • Get rid of expected.origin and rely only on the newer expected.domain
  • The debug flag will be renamed verbose
  • make a triple build (modules, commonjs, browser script)

...I also wonder if I should simply rename the "webauthn v2.0" into "passkeys v1.0" instead.

In case you have something else that you'd like to see changed/improved, now is the time to speak up!

Compatibility with 1Password and Bitwarden

I have been testing with this library and it really makes authentication via WebAuthn simple to use. I've tested it through Chrome passkey storage, Apple KeyChain (FaceID) storage and it all works perfectly. Recently both 1Password and Bitwarden added support for saving and using passkeys and I am running into issues with that. I was a bit sad that I could not get it to work, because once my password manager supports it I feel confident enough to actually start using it instead of using it more like a gimmick :)

In 1Password I straight up get an error during a passkey registration breaking on an unimplemented internal function response.getPublicKey. When I test with Bitwarden it seems to work, but upon further inspection the public key is an empty string:

{ id: 'NmqWB5ibSYef0hoShh9Zyw', publicKey: '', algorithm: 'ES256' }

Weirdly enough, the serverWebAuthn.verifyRegistration on the server side succeeds with this empty public key but logging in does not, breaking on DataError: Invalid keyData, which seems logical.

Getting to the point of this issue: I know WebAuthn support is in its early phase, certainly for these password managers. I get the feeling that both managers do not support this response.getPublicKey function (yet). But these password managers have tested their setup and felt confident enough to release it to the public, which gives me the idea that there are other ways to get the public key that are possibly better supported among WebAuthn providers.

Are you aware of other ways to do this, and what are your thoughts on this?

Edit: I was actually able to login to Github with a passkey, which proves that the Bitwarden implementation is usable

Signature verification of ES256 fails

According to the specs, it should be:

-7 (ES256), where kty is 2 (with uncompressed points) and crv is 1 (P-256).

However it is unclear how this "kty is 2 (with uncompressed points)" is applied. In the browser's Crypto module, there is no such parameter for ECDSA Keys, only for the named curve.

This kind of makes it impossible for the browser to verify signatures in the demo, or a big crypto lib should be added as dependency.

Note: Recently, Windows Hello switched from RS256 to ES256, dunno exactly when.

[Next 13] 'window' is not defined

It happens in a Next 13 page (app folder) marked as a client component with use client directive.

Imported as: import * as webauthn from "@passwordless-id/webauthn";
Used as: {webauthn.client.isAvailable() && "available"}

Allow passing `authenticatorSelection.residentKey` option in `client.register`

Which problem is this feature request solving?
In my login flow, I do not want the user to have to type a username. So I pass an empty array of credentialIds to client.authenticate. This unfortunately does not work in this library because the created passkeys are not a "Client-side discoverable Credential". To create "Client-side discoverable Credentials", the residentKey option in authenticatorSelection during registration needs to be set to "preferred" or "required".

Currently it is not possible to set the authenticatorSelection.residentKey option in client.register

This login flow is also mentioned in your README.md, but as far as I'm aware, not currently possible to create with this library.

Describe the solution you'd like
A new option in the third parameter of client.register (RegisterOptions) for residentKey, to set a value for authenticatorSelection.residentKey in creationOptions.

Describe alternatives you've considered
Maybe a generic override using a Partial<PublicKeyCredentialCreationOptions> interface would be preferred, this way any option can be changed without needing a code change every time someone wants a new option to be supported.

Can you submit a pull request?
Yes, if the feature is approved.

Unexpected RpIdHash

I am implementing webauthn in chrome extension, registration works, authentication fails with Unexpected RpIdHash: foo vs bar

client:

async function authenticate() {
    const challenge = await ky
        .create({ prefixUrl: process.env.API_URL, credentials: 'include' })
        .post('auth/challenge', { json: { username: 'testuser' } })
        .json<{ challenge: string; id?: string }>();

    const authentication = await client.authenticate(challenge.id ? [challenge.id] : [], challenge.challenge, {
        authenticatorType: 'auto',
        userVerification: 'required',
        mediation: 'required',
        timeout: 60000,
        debug: false,
    });

    await ky
        .create({ prefixUrl: process.env.API_URL, credentials: 'include' })
        .post('auth/login', { json: { authentication } })
        .json<{ message: string }>();
}

server:

  const authentication = req.body.authentication as AuthenticationEncoded

  const credential = await db.credential.findUniqueOrThrow({ where: { credentialId: authentication.credentialId } })

  await server.verifyAuthentication(
    authentication,
    {
      id: credential.credentialId,
      algorithm: credential.algorithm,
      publicKey: credential.publicKey,
    },
    {
      challenge: req.signedCookies.challenge,
      origin: 'chrome-extension://id',
      userVerified: false,
      verbose: true,
    },
  )

ReferenceError: crypto is not defined

I'm running this library with Firebase Functions and for some reason it's unable to use the crypto library that should be built-in in Node v20.

More detailed logs:

ReferenceError: crypto is not defined
at parseCryptoKey (file:///workspace/node_modules/@passwordless-id/webauthn/dist/esm/server.js:92:5)
    at verifySignature (file:///workspace/node_modules/@passwordless-id/webauthn/dist/esm/server.js:107:27)
    at Module.verifyAuthentication (file:///workspace/node_modules/@passwordless-id/webauthn/dist/esm/server.js:31:36)

Sponsor Link

It looks like the sponsorship link isn't working, and isn't available on each repo.

User statement:
As a possible end user of passworless.id to determine if I want to use this as part of my application I want to check if the product is actively maintained, and use the sponsorship area to see if there's support from other organizations and individuals.

I love the idea of a public IDP and would like to support this project.

sidenote: have you gathered support from any organizations? I understand this is in alpha, but just wanted to gauge external interest

[BUG]- VerifyAuthentication not working as expected

When I call the verifyAuthentication function with the given parameters, it does not work as expected.

server.verifyAuthentication(
    resp,
    credentialKey,
    expected
  );

It displays an error => An error occurred: crypto is not defined.

Can anyone help me solve this issue?

I am guessing there is a missing dependency injection somewhere in the codebase.

Suggestion: Add a demo that consumes NPM package

Related to #2. I think there actually is an issue with NPM package but it was never diagnosed because the neither of the demo applications use the NPM package. The both reference the script via relative path such as:

import * as webauthn from '../../dist/webauthn.min.js'

I think the current demos function by direction serving the contents of the demos folder. However, I think to use the NPM package you would need the demo to have some build step which installs the package and then generates a site from it.

Certainly a more complex, but it would provide value that the package works so I think it could be good investment.

Customizing displayName and potential PII leak in user handle

First off thanks for the work on this library, I can see a lot of thought has gone into it and the tools around it.

I have a question regarding the username parameter when registering on the client, specifically this line.

// ID should not be directly "identifiable" for privacy concerns
id: await utils.sha256(new TextEncoder().encode(username))

https://github.com/passwordless-id/webauthn/blob/main/src/client.ts#L84

A plain client side sha256 would still be seen as PII as no secret salt was included from the server. I double checked the spec after seeing this and it confirmed it.

Relying Party MUST NOT include personally identifying information, e.g., e-mail addresses or usernames, in the user handle. This includes hash values of personally identifying information, unless the hash function is salted with salt values private to the Relying Party, since hashing does not prevent probing for guessable input values.

https://w3c.github.io/webauthn/#sctn-user-handle-privacy

If I'm correct and not misread your code, then I believe you'll need to open up the registration to allow provision of the handle + the PII (displayName, name) separately, and make it clear the handle must not be a raw username or similar.

You may want to let user's of the library know this issue as PII could possibly be leaked (may need GDPR disclosures for those businesses?) so would need clients to re-register customer passkeys? I'm not entirely sure the flows there, still getting to grips with those.

Issue with ES Modules in firebase functions

Current behavior:
Works fine in express in a traditional nodejs app

When deployed to firebase, tried both ts and vanilla js, getting this error. Have tried a number of workaround

src/index.ts:14:27 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@passwordless-id/webauthn")' call instead.
To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to ____unctions/package.json'.

The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@passwordless-id/webauthn")' call instead.
To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field "type": "module" to '/_____unctions/package.json'.ts(1479)

Errors

____.min.js:1 Uncaught (in promise) FirebaseError: require() of ES Module
/workspace/node_modules/@passwordless-id/webauthn/dist/esm/index.js from /workspace/lib/index.js not supported.

Instead change the require of /workspace/node_modules/@passwordless-id/webauthn/dist/esm/index.js in /workspace/lib/index.js to a dynamic import() which is available in all CommonJS modules.

Also got this one
code: 'ERR_REQUIRE_ESM'

Server should not log to console.debug

verifySignature() logs various things to console.debug() but there is no setting to turn this off other than overriding console.debug

Some kind of setting would be helpful here because debug info isn't required most of the time.

Thanks for the awesome library ๐Ÿ˜ƒ

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.