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 · 15 comments
Open

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

JamesCullum opened this issue Apr 11, 2020 · 15 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.

@rdubigny
Copy link

rdubigny commented Jun 14, 2024

I am having difficulty getting this to work. Here is my Cypress test:

describe("sign-in with webauthn on untrusted browser", () => {
  before(async () => {
    await Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.enable",
    });
  });

  it("should sign-in with webauthn", function () {
    Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.addVirtualAuthenticator",
      params: {
        options: {
          protocol: "ctap2",
          transport: "internal",
          hasResidentKey: true,
          hasUserVerification: true,
          isUserVerified: true,
        },
      },
    }).then(({ authenticatorId }) => {
      Cypress.automation("remote:debugger:protocol", {
        command: "WebAuthn.addCredential",
        params: {
          authenticatorId,
          credential: {
            credentialId: "xxx",
            isResidentCredential: true,
            userHandle: "MQ==",
            rpId: "localhost",
            privateKey: "xxx",
            signCount: 0,
          },
        },
      });
    });

    cy.visit(`http://localhost:4001`);
    cy.get("button.moncomptepro-button").click();
    cy.get('[href="/users/sign-in-with-passkey"]')
      .contains("Se connecter avec une clé d’accès")
      .click();

    cy.contains("Se connecter avec une clé d’accès");

    cy.get("#webauthn-btn-begin-authentication").contains("Continuer").click();
  });
});

However, I encountered the following error in the Cypress logs:

The 'publickey-credentials-get' feature is not enabled in this document.
Permissions Policy may be used to delegate Web Authentication capabilities to cross-origin child frames.

The documentation suggests adding an attribute to the Cypress iframe, but I am unsure how to do this. Any help would be gladly appreciated.

Thank you!

@mpirli
Copy link

mpirli commented Nov 11, 2024

I'm trying to get a Cypress test to work with WebAuthn, using Chrome DevTools Protocol and and the examples here. The UI is as follows: you click on the 2FA method you want (Biometrics), it redirects you to a new page which initiates WebAuthn registration, and on success you are redirected to a success page.
I have written the following functions:

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

function simulateSuccessfulPasskeyInput(authenticatorId: string){
  console.log("Simulating")
  return Cypress.automation("remote:debugger:protocol", {
    command: "WebAuthn.setUserVerified",
    params: {
      authenticatorId,
      isUserVerified: true,
    },
  }).then( () => {
    console.log("step 2 "+authenticatorId)
    return Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.setAutomaticPresenceSimulation",
      params: {
        authenticatorId,
        enabled: true,
      },
    });
  }).then(() => {
    console.log("trigger op "+authenticatorId)
    return ;
  }).then (() => {
    return Cypress.automation("remote:debugger:protocol", {
      command: "WebAuthn.setAutomaticPresenceSimulation",
      params: {
        authenticatorId,
        enabled: false,
      },
    });
  })
}

and the test step is

addVirtualAuthenticator()
      .then((authenticatorId) => {
        console.log("to simulation")
        console.log(authenticatorId)
        simulateSuccessfulPasskeyInput(authenticatorId)
      })

And it doesn't work. I get to the page that is supposed to do the validation and the fingerprint popup doesn't appear, I just get a message "Biometrics registration failed". I have tried adding the Virtual Authenticator with automaticPresenceSimulation=true at the beginning of the Scenario, and skipping the simulation step, but I still get the same. The scenario in (https://www.corbado.com/blog/passkeys-e2e-playwright-testing-webauthn-virtual-authenticator) includes an operationTrigger(), the user clicks somewhere in order to start the registration, which is something that doesn't exist in my code (registation starts on page load). But I would expect setting automaticPresenceSimulation to true would solve that problem. Any help appreciated.
Update: I have realised that I run into the same problem as @rdubigny, "The 'publickey-credentials-get' feature is not enabled in this document. Permissions Policy may be used to delegate Web Authentication capabilities to cross-origin child frames." My document doesn't have an iframe, but in Cypress I see there are 2 frames under "top": Your Spec: '/__cypress/iframes/cypress%2Fcucumber etc. and Your project: 'Test Project' (webauthn).
The YourSpec frame has publickey-credentials-create, publickey-credentials-get as Allowed. The Your Project frame has them as disabled. Our Cypress configuration has

  chromeWebSecurity: false,
  experimentalModifyObstructiveThirdPartyCode: true,

I don't know what else we should do.

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