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.97 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 71.83% JavaScript 0.46% Python 9.23% Shell 0.30% HTML 17.84% CSS 0.34%
authentication passkeys passwordless webauthn

webauthn's Issues

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),
}

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

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.

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.

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

[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"}

Error: DOMException: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission

I get Error: DOMException: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission When trying to do this:

const response = await fetch(${apiUrl}/api/challenge?${params.toString()}, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error('Network response was not ok');
}

const challenge = await response.text();
console.log('Challenge:', challenge);
try {
const registration = await client.register(${formData.firstName} ${formData.lastName}, challenge, {
authenticatorType: "auto",
userVerification: "required",
timeout: 60000,
attestation: true,
userHandle: "",
debug: false
})
console.log('Registration:', registration);

these are the logs:
Result: 99b5c557-51d3-4945-a9b5-999889cc6b77 [index.tsx:139:10](webpack://_N_E/pages/add/index.tsx?2ab1)

Error: DOMException: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

ReferenceError: crypto is not defined

I have setup @passwordless-id/webauthn and want to use the server as described in the docs to verify the authentication with server.verifyAuthentication(...). Sadly, I get the following error:

ReferenceError: crypto is not defined
    at null.parseCryptoKey (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/@passwordless-id/webauthn/dist/esm/server.js:93:5)
    at null.verifySignature (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/@passwordless-id/webauthn/dist/esm/server.js:108:27)
    at Object.verifyAuthentication (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/@passwordless-id/webauthn/dist/esm/server.js:30:36)
    at null.<anonymous> (/Users/rogerkreienbuehl/Projekte/apmatic-next/apps/apmatic-next-oauth/src/routes/webauthn.ts:58:53)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at null.noCache (/Users/rogerkreienbuehl/Projekte/apmatic-next/apps/apmatic-next-oauth/src/middleware/no-cache.middleware.ts:6:3)
    at null.contextEnsureOidc (/Users/rogerkreienbuehl/Projekte/apmatic-next/apps/apmatic-next-oauth/src/routes/helpers/ensureOidc.ts:4:5)
    at async serve (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/koa-static/index.js:53:9)
    at null.<anonymous> (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/oidc-provider/lib/helpers/initialize_app.js:222:5)
    at null.errorHandler (/Users/rogerkreienbuehl/Projekte/apmatic-next/node_modules/oidc-provider/lib/shared/error_handler.js:26:7)

Is there anything I missed?

I use node version v20.10.0 and @passwordless-id/webauthn version v1.6.1.

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)

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,
    },
  )

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 😃

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.

Missing crypto import?

Hi there,

I'm trying to use this module with Node v16.16.0. Doing

    const auth = await server.verifyAuthentication(payload.authentication, key.credential, challenge)

I'm getting:

ReferenceError: crypto is not defined
    at parseCryptoKey (file:///home/asbjorn/srv/chainborn/v3/api/node_modules/@passwordless-id/webauthn/dist/esm/server.js:93:5)
    at verifySignature (file:///home/asbjorn/srv/chainborn/v3/api/node_modules/@passwordless-id/webauthn/dist/esm/server.js:108:27)

And even adding import crypto from 'crypto' to the server.js file it still fails since there is no crypto.subtle 😅 🤔

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?

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:
...

Error: ReferenceError: module is not defined in ES module scope in SvelteKit/Vite

Thank you for the big V2 update! While migrating, I encountered an error and reproduced it on a clean install with SvelteKit:

10:48:46 AM [vite] Error when evaluating SSR module /src/routes/+page.svelte: failed to import "@passwordless-id/webauthn"
|- ReferenceError: module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/Users/hj/Documents/dev/passwordless/node_modules/@passwordless-id/webauthn/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///Users/hj/Documents/dev/passwordless/node_modules/@passwordless-id/webauthn/dist/cjs/index.js:30:1
    at ModuleJob.run (node:internal/modules/esm/module_job:262:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:474:24)
    at async nodeImport (file:///Users/hj/Documents/dev/passwordless/node_modules/vite/dist/node/chunks/dep-mCdpKltl.js:52735:15)
    at async ssrImport (file:///Users/hj/Documents/dev/passwordless/node_modules/vite/dist/node/chunks/dep-mCdpKltl.js:52591:16)
    at async eval (/Users/hj/Documents/dev/passwordless/src/routes/+page.svelte:4:31)
    at async instantiateModule (file:///Users/hj/Documents/dev/passwordless/node_modules/vite/dist/node/chunks/dep-mCdpKltl.js:52650:5)
# reproduce
npm create svelte@latest my-app
cd my-app
npm install
npm install @passwordless-id/webauthn
npm run dev
<!-- /src/routes/+page.svelte -->
<script lang="ts">
  import { client } from "@passwordless-id/webauthn";

  async function registerUser() {
    await client.register({
      challenge: "a random string generated by the server",
      user: "John Doe",
    });
  }
</script>

<h1>Welcome to SvelteKit</h1>
<p>
  Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation
</p>
  System:
    OS: macOS 14.5
    CPU: (8) arm64 Apple M2
    Memory: 3.50 GB / 24.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.3.0 - /usr/local/bin/node
    npm: 10.8.2 - /usr/local/bin/npm
    pnpm: 8.7.6 - /usr/local/bin/pnpm
    bun: 1.1.20 - ~/.bun/bin/bun
  Browsers:
    Brave Browser: 127.1.68.128
    Chrome: 126.0.6478.127
    Safari: 17.5
  npmPackages:
    @passwordless-id/webauthn: ^2.0.0 => 2.0.0 
    @sveltejs/adapter-auto: ^3.0.0 => 3.2.2 
    @sveltejs/kit: ^2.0.0 => 2.5.18 
    @sveltejs/vite-plugin-svelte: ^3.0.0 => 3.1.1 
    svelte: ^4.2.7 => 4.2.18 
    vite: ^5.0.3 => 5.3.5 

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.

[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.

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'

Passwordless login with passkeys

I'm currently onboarding customers using the @simplewebauthn package, but I find the process to be a bit too verbose for my use case.

I wish to give customers the ability to register and login to their accounts only by means of Passkeys. I don't plan on supporting passwords or magic link logins because, in my limited understanding of the protocol, Passkeys should provide the ability to login without any user identifier.

Wanting to know if the webauthn package under the passwordless-id would let me achieve this. My main friction points with the current implementation is that I need to shuffle around and coordinate registration using jwts and then request the user to imput an identifier such as an email address. I think this is redundant and Passkeys solve this.

Are the examples in the repo demonstrating this functionality, both on the server and on the client?

I guess that I'm trying to ask for a clear example because this is a new paradigm shift, at least in my mind, where we receive the credential from the client side and we have to store those against a user. Subsequently, I assume we can pass during authentication an empty array for credentials and leave the authenticator to discover them (?)

I think this is exactly what I'm trying to achieve https://stackoverflow.com/q/73562080

Thanks and sorry if this was asked before

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

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.

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'
  }
}

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.

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

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>"
}

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

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!

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?

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!

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

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.

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.