Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: WebAuthn/FIDO2 testing via virtual authenticators #6991

Open
JamesCullum opened this issue Apr 11, 2020 · 13 comments
Open

Feature Request: WebAuthn/FIDO2 testing via virtual authenticators #6991

JamesCullum opened this issue Apr 11, 2020 · 13 comments
Labels
E2E Issue related to end-to-end testing type: feature New feature that does not currently exist

Comments

@JamesCullum
Copy link

JamesCullum commented Apr 11, 2020

Current behavior:

Cypress does not support any way to test WebAuthn / FIDO2 flows without mocking the authenticator, which does not allow to test all cryptographic interactions sufficiently. If a valid request is forwarded to the browser, it will be delegated out of the browser and can not be interacted with.

Desired behavior:

Just like Selenium implemented it end of last year, Cypress should offer a way to use the W3C automation API to programatically interact with the browser to manage virtual authenticators that can confirm actions without leaving the browser context.

@cypress-bot cypress-bot bot added the stage: proposal 💡 No work has been done of this issue label Apr 13, 2020
@jennifer-shehane jennifer-shehane added type: feature New feature that does not currently exist stage: proposal 💡 No work has been done of this issue and removed stage: proposal 💡 No work has been done of this issue labels Apr 13, 2020
@JamesCullum
Copy link
Author

JamesCullum commented Apr 13, 2020

I was able to implement this using the chrome debugger, using code examples from the Google virtual authenticator extension and @gabbersepp blog post on how to access the low level API in Cypress.

For anyone curious, you can see the result in the script here and the setup as plugin here.

Should we aim to have this documented or put it into the sink? Having a Cypress-native interaction with the chrome driver might be useful for this as well.

@jennifer-shehane
Copy link
Member

@JamesCullum Yes, this could potentially work as a example recipe if a good example can be extracted out.

@aaujayasena
Copy link

aaujayasena commented Mar 7, 2022

Most of the software access management contains the facility of authentication with security key and biometric identifications. Having this feature with your product will be a great since now traditional username and password authentications are not much popular.

Hope cypress can consider this requirement in high priority.

@cypress-bot cypress-bot bot added stage: backlog and removed stage: proposal 💡 No work has been done of this issue labels Apr 29, 2022
@thiagonzalez
Copy link

2 years have passed since you started this discussion. Is this a reality or still a proposal?
We are building a project with just webauthn as the authentication method, so it would be nice to have a way to test it.
Let me know what you guys have done :)

@JamesCullum
Copy link
Author

As mentioned, above is an example that you can use for it. Only thing missing is Cypress adding it as official example.

@thiagonzalez
Copy link

@JamesCullum this may be a dumb question, but I'm kind of lost of what should I host on 127.0.0.1:9222 to receive the CRI requests.

I replicated your sendCRI task as follows:

const CRI = require("chrome-remote-interface");

module.exports = (on, config) => {
  on("task", {
    async sendCRI(args) {
      criClient = await CRI();
      return criClient.send(args.query, args.opts);
    },
  });

  return config;
}

And I'm getting this error:

image

I know you've mentioned the plugin Virtual Authenticators Tab (link) from Google, but how exactly you started this process on port 9222? I read their documentation, but I couldn't find this procedure anywhere.

Thanks for sharing your project with us! 😃

@kishore8
Copy link

kishore8 commented Jun 9, 2022

Hi @JamesCullum, I keep getting this exception, when trying to write e2e tests for biometric registration on my device.I guess I am unable to register the biometric key because of it. how do we overcome this? TIA!
I am trying to do something like this in my code - window.navigator.credentials.create({ publicKey });

Screenshot 2022-05-19 at 7 16 43 PM

@hcnode
Copy link

hcnode commented Oct 5, 2022

I was working on webauthn related in cypress, I found actually cypress already provides Cypress.automation for Low level access to Chrome Debugger Protocol, so we don't need to use 3rd party library to do this. here are the code to enable, create and remove virtual authenticator:

addVirtualAuthenticator() {
    return Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.enable",
      params: {},
    }).then((result) => {
      console.log("WebAuthn.enable", result);
      return Cypress.automation("remote:debugger:protocol", {
        command: "WebAuthn.addVirtualAuthenticator",
        params: {
          options: {
            protocol: "ctap2",
            transport: "internal",
            hasResidentKey: true,
            hasUserVerification: true,
            isUserVerified: true,
          },
        },
      }).then((result) => {
        console.log("WebAuthn.addVirtualAuthenticator", result);
        return result.authenticatorId;
      });
    });
  }
removeVirtualAuthenticator(authenticatorId) {
    Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.removeVirtualAuthenticator",
      params: {
        authenticatorId,
      },
    }).then((result) => {
      console.log("WebAuthn.removeVirtualAuthenticator", result);
    });
  }

@Andy2003
Copy link

Andy2003 commented May 8, 2023

I wrote a Blogost about how I solved this issue with Cypress, Active Directory and Ping Identity

@nagash77 nagash77 added the E2E Issue related to end-to-end testing label May 8, 2023
@Brij-M
Copy link

Brij-M commented Jan 9, 2024

I was working on webauthn related in cypress, I found actually cypress already provides Cypress.automation for Low level access to Chrome Debugger Protocol, so we don't need to use 3rd party library to do this. here are the code to enable, create and remove virtual authenticator:

addVirtualAuthenticator() {
    return Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.enable",
      params: {},
    }).then((result) => {
      console.log("WebAuthn.enable", result);
      return Cypress.automation("remote:debugger:protocol", {
        command: "WebAuthn.addVirtualAuthenticator",
        params: {
          options: {
            protocol: "ctap2",
            transport: "internal",
            hasResidentKey: true,
            hasUserVerification: true,
            isUserVerified: true,
          },
        },
      }).then((result) => {
        console.log("WebAuthn.addVirtualAuthenticator", result);
        return result.authenticatorId;
      });
    });
  }
removeVirtualAuthenticator(authenticatorId) {
    Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.removeVirtualAuthenticator",
      params: {
        authenticatorId,
      },
    }).then((result) => {
      console.log("WebAuthn.removeVirtualAuthenticator", result);
    });
  }

Hi, It didn't work for me. WebAuthn.addVirtualAuthenticator didn't return the authenticatorid. result is undefined. is there any additional settings required. However I tried https://webauthn.io to verify, it did open the authenticator pops which doesn't happen if I enable webauthn via UI

@tylerccarson
Copy link

tylerccarson commented Feb 9, 2024

I'm having the save problem as @Brij-M, the WebAuthn commands don't seem to register with the chrome debugger protocol. If I run "WebAuthn.enable" manually from the Protocol monitor, I get the following error: { "code": -32601, "message": "'WebAuthn.enable' wasn't found" } or just an empty {}. And the WebAuthn devtool panel doesn't enable the virtual authenticator environment.

Did Chrome possibly pull support for this? Or am I missing something. Basically wondering if this is still working for others @hcnode @JamesCullum

Update: it seems to be working now when using the Cypress.automation API as described. For whatever reason these commands aren't viewable in the monitor, but I am getting the expected responses and able to use the virtual authenticator during a Cypress test.

@harrygreen
Copy link

Hey @tylerccarson, would you mind posting an example of how you got these working?

@tylerccarson
Copy link

Hey @tylerccarson, would you mind posting an example of how you got these working?

@harrygreen I really didn't add anything novel here except wrapping hcnode's examples in Cypress commands, and fleshing out more functions for adding credentials as well. Chrome's documentation is a good reference for seeing what's possible: CDP Webauthn

Here's one example I added for cy.addCredential() (which relies on WebAuthn.enable and having created a virtual authenticator).

/** https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/#type-Credential */
export type TestWebauthnCredential = {
  credentialId: string
  isResidentCredential: boolean
  rpId?: string // required when adding
  privateKey: string // ECDSA P-256 private key in PKCS#8 format, encoded in base64
  userHandle: string // userId, byte sequence
  signCount: number
}

Cypress.Commands.add('addCredential', (authenticator, credential) => {
  Cypress.log({})
  return cy.wrap(
    new Promise<void>(async (resolve, reject) => {
      if (!authenticator.id) {
        return reject(new Error('addCredential() called with no authenticatorId'))
      }

      // add credential
      await Cypress.automation('remote:debugger:protocol', {
        command: 'WebAuthn.addCredential',
        params: {
          authenticatorId: authenticator.id,
          credential,
        },
      })

      // get credential back
      const getCredentialResult = (await Cypress.automation('remote:debugger:protocol', {
        command: 'WebAuthn.getCredential',
        params: {
          authenticatorId: authenticator.id,
          credentialId: credential.credentialId,
        },
      })) as {credential: TestWebauthnCredential}

      // ensure it was added and resolve
      const wasAdded = !!getCredentialResult.credential
      if (wasAdded) {
        resolve()
      } else {
        reject(new Error('Credential was not added'))
      }
    }),
    {log: false}
  )
})

I found getting the credential after adding to check helpful since like I mentioned, the actual debugger UI doesn't seem to update to reflect the results of these automated commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E2E Issue related to end-to-end testing type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests