3

In my use case, there is a registration page that triggers the browser-specific webauthn flow. For example in Chrome on a Mac you will see this series of popups:

  1. Pick an option between USB security key and Built-in sensor
  2. MacOS confirmation with Touch ID
  3. Confirmation dialog from Chrome requesting access to your security key

Besides https://w3c.github.io/webauthn/#add-virtual-authenticator I haven't found much documentation about authenticating with webauthn as part of a selenium test. What resources are available to help devs figure out how to test webauthn with Selenium in JavaScript? I have also checked out https://github.com/SeleniumHQ/selenium/issues/7829 but the example test case does not make sense to me. Examples would be hugely appreciated.

Update with solution for js:

  import { Command } from 'selenium-webdriver/lib/command';

  addVirtualAuthenticator = async () => {
    await this.driver.getSession().then(async session => {
      this.driver
        .getExecutor()
        .defineCommand('AddVirtualAuthenticator', 'POST', `/session/${session.id_}/webauthn/authenticator`);

      let addVirtualAuthCommand = new Command('AddVirtualAuthenticator');
      addVirtualAuthCommand.setParameter('protocol', 'ctap2');
      addVirtualAuthCommand.setParameter('transport', 'internal');
      addVirtualAuthCommand.setParameter('hasResidentKey', true);
      addVirtualAuthCommand.setParameter('isUserConsenting', true);
      await this.driver.getExecutor().execute(addVirtualAuthCommand);
    });
  };

Note that this.driver is of type WebDriver.

Call addVirtualAuthenticator before hitting any code that interacts with navigator (in our case user registration involved a call to navigator.credentials.create). If you need access to the publicKey, i.e. via navigator.credentials.get({ publicKey: options }) during login, then hasResidentKey is critical.

mkhbragg
  • 131
  • 3
  • 12

2 Answers2

5

A good resource for an example if you're implementing this in java and using selenium 4 is the tests on selenium itself. You basically need to

  • Create a virtual authenticator

    In your case, you should set the transport to internal and hasUserVerification to true to simulate touchID.

VirtualAuthenticatorOptions options = new VirtualAuthenticatorOptions();
options.setTransport(Transport.INTERNAL)
       .hasUserVerification(true)
       .isUserVerified(true);
VirtualAuthenticator authenticator =
    ((HasVirtualAuthenticator) driver).addVirtualAuthenticator(options);
  • Perform the action that triggers registration.

    If everything goes right, the browser should not show a dialog. Instead, it should immediately return a credential.

For any other language or selenium version, you will need to drop into calling the WebDriver protocol directly. As you pointed out, the W3C spec has documentation on the protocol endpoints.

For java, it might be something like

browser.driver.getExecutor().defineCommand(
    "AddVirtualAuthenticator", "POST", "/session/:sessionId/webauthn/authenticator");

// ...

Command addVirtualAuthCommand = new Command("AddVirtualAuthenticator");
addVirtualAuthCommand.setParameter("protocol", "ctap2");
addVirtualAuthCommand.setParameter("transport", "usb");
browser.driver.getExecutor().execute(addVirtualAuthCommand);

For javascript, you should be able to use defineCommand and webDriver.execute in a similar fashion.

Nina Satragno
  • 561
  • 3
  • 8
  • 1
    Thanks for this helpful info! I was able to get registration working with a javascript-ified version of the sample java code you provided. Interestingly, although registration works, attempting to subsequently login with the newly created account fails, pointing me towards https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client. I am still debugging this and will give an update when I know more. Thanks again for your help! – mkhbragg Aug 21 '20 at 13:42
  • It's possible the authenticator is being created for a different frame than the one you're trying to assert from. This happens if you are trying to exercise the credential from a different frame tree (e.g. from another browser instance, or from a parent iframe). What happens if you call the GetCredentials command? – Nina Satragno Aug 21 '20 at 16:09
  • Ahhh I could see that being the problem. Calling GetCredentials from the tests returns an empty array. Investigating more today. – mkhbragg Aug 24 '20 at 13:51
  • 1
    Aha! On login, the UI is calling `navigator.credentials.get({ publicKey: options })`, so for the virtual authenticator to work I needed to include `addVirtualAuthCommand.setParameter('hasResidentKey', true);` Such a simple fix, so long to figure it out. But it works now! Still sorting out the test structure, so I'll update with more complete findings once I'm happy with that. – mkhbragg Aug 27 '20 at 16:06
  • 1
    Thanks @NinaSatragno for your awesome contributions in this regard :) – Saikat Aug 17 '21 at 10:00
-2

This is worst practice in selenium

Two Factor Authentication shortly know as 2FA is a authorization mechanism where One Time Password(OTP) is generated using “Authenticator” mobile apps such as “Google Authenticator”, “Microsoft Authenticator” etc., or by SMS, e-mail to authenticate. Automating this seamlessly and consistently is a big challenge in Selenium. There are some ways to automate this process. But that will be another layer on top of our Selenium tests and not secured as well. So, you can avoid automating 2FA.

There are few options to get around 2FA checks:

1.Disable 2FA for certain Users in the test environment, so that you can use those user credentials in the automation. 2.Disable 2FA in your test environment. 3.Disable 2FA if you login from certain IPs. That way we can configure our test machine IPs to avoid this.

Félix Paradis
  • 5,165
  • 6
  • 40
  • 49
Justin Lambert
  • 940
  • 1
  • 7
  • 13
  • The question is about WebAuthn (not authenticator apps), for which selenium 4+ has support. You may also want to test the 2FA as part of your E2E suite. – Nina Satragno Aug 19 '20 at 16:17