microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
67.32k stars 3.71k forks source link

[Feature] support WebAuthn #7276

Open SphiwokuhleS opened 3 years ago

SphiwokuhleS commented 3 years ago

Is there a way to test FIDO2 CTAP2 Webauthn?

### Tasks
aslushnikov commented 3 years ago

There's no Webauthn support yet in Playwright. But Please upvote if you'd like to see one!

xsoft7 commented 2 years ago

Just want to add a note, that I've implemented virtualAuthenticator (based ofcourse on WebAuthn) in Python+Selenium.

Trying to do the same on PlayWright (in TypeScript) - the obstacle is that it should have "Webdriver" support, as it seems (according to official documentation of WebAuthn) that it needs it.

The implementation is done via REST api commands to the driver.

please let me know if there any progress

dbalikhin commented 2 years ago

Any progress by any chance? :)

barrysimpson commented 1 year ago

While it would certainly be nicer to have Playwright APIs that provide normalized WebAuthn virtual authenticator functionality across all three browsers, it's possible to interact with WebAuthn virtual authenticators in Chromium-only tests using the Chromium Dev Tools protocol's WebAuthn messages and Playwright's CDPSession.

For example, to create a FIDO2/CTAP2 virtual authenticator that automatically simulates user presence and verification, do something like this:

const cdpSession = await page.context().newCDPSession(page);
await cdpSession.send('WebAuthn.enable');
await cdpSession.send('WebAuthn.addVirtualAuthenticator', {
    options: {
        protocol: 'ctap2',
        ctap2Version: 'ctap2_1',
        hasUserVerification: true,
        transport: 'usb',
        automaticPresenceSimulation: true,
        isUserVerified: true,
    }
});

// Interact with your site to trigger navigator.credentials.create(), and the virtual authenticator will automatically answer all the necessary prompts for registration.

If Playwright provided a way to use the WebDriver protocol directly with Firefox or Safari, similar to CDPSession, it might be possible to create workarounds for those browsers too without first party Playwright support. But that doesn't seem to be possible.

sumitdnaik commented 1 year ago

Any progress on this? Can anyone help with the update on this? Please help understand the status of WebAuthn support as we want to automate authentication flows @aslushnikov

aslushnikov commented 1 year ago

@sumitdnaik there's been no progress regarding WebAuthn automation in Playwright as of May 30, 2023

sumitdnaik commented 1 year ago

Thank you for the reply! We had chosen Playwright to automate our login flows and WebAuthn is an important part of 2FA flows we have. Is there any alternate way/workaround to automate this using Playwright? If anyone can help, it would be great. @SphiwokuhleS @dbalikhin Would like to understand what approach you took.

strusbergAlex commented 1 year ago

Hi barrysimpson, I was unable to get it to work. I used your code snippet and I saw that the "Enable virtual authenticator environment" is still disabled in the browser that opened. Any thoughts maybe ? I am using latest chrome and Playwright

jcarranzan commented 1 year ago

Hi, any progress, update or workaround regarding WebAuthn automation in Playwright @aslushnikov @yury-s ? Thank you

banshee commented 11 months ago

I was unable to get it to work. I used your code snippet and I saw that the "Enable virtual authenticator environment" is still disabled in the browser that opened. Any thoughts maybe ? I am using latest chrome and Playwright

Same for me. I can do it manually by going into chrome dev tools and turning it on in the UI, but that code snippet doesn't seem to do anything.

To get to the manual settings, go to the three dot menu and choose WebAuth, then click enable:

image
barrysimpson commented 11 months ago

@banshee hard to say what could be going on without seeing an example test. It continues to work for me on the latest playwright version.

Something that might be worth noting is that I create a new CDPSession, enable WebAuthn, and add a new virutal authenticator inside every single test() block. I do not do this in beforeEach() or beforeAll(), which would certainly be tempting. I don't recall exactly why. But BrowserContext.newCDPSession() requires a Page or Frame parameter, which implies that the CDPSession is tied to a particular Page instance, and that implies subsequent new Page instances may not automatically inherit the CDPSession and the WebAuthn API's enabled state of a previous Page instance. Since each test() creates a new Page instance for you, you probably have to do the virtual authenticator setup work inside every test.

banshee commented 11 months ago

@barrysimpson your advice was spot on. I was calling your code snippet before visiting the page. It works now.

Future readers should note that the UI does not show authenticators added this way as far as I can see.

alex-radulescu-b1 commented 10 months ago

Please consider adding webauthn support also, as this is becoming more and more mainstream and because some of our apps only use webauthn, there is no workaround for them and thus preventing us from moving to Playwright

Zerotask commented 8 months ago

Would be really nice to have this added to Playwright.

armspkt commented 8 months ago

example.spec.js

const { chromium } = require("playwright");
const { test, expect } = require("@playwright/test");
const {
  enableWebAuthn,
  addVirtualAuthenticator,
  removeVirtualAuthenticator,
  addCredential,
  getCredentials,
} = require("../src/webauthn");

test("test passkeys example.com", async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  // Create a new CDP session
  const client = await context.newCDPSession(page);

  await enableWebAuthn(client);
  const authenticatorId = await addVirtualAuthenticator(client);
  await addCredential(client, authenticatorId);
  await getCredentials(client, authenticatorId);
  await removeVirtualAuthenticator(client, authenticatorId);

});

webauth.js

const CREDENTIAL_ID = "";

const enableWebAuthn = (client) => {
  return client.send("WebAuthn.enable");
};

const addVirtualAuthenticator = (client) => {
  return client
    .send("WebAuthn.addVirtualAuthenticator", {
      // config authenticator
      options: {
        protocol: "ctap2",
        transport: "usb",
        hasResidentKey: true,
        hasUserVerification: true,
        isUserVerified: true,
      },
    })
    .then((result) => {
      console.log("WebAuthn.addVirtualAuthenticator", result);
      return result.authenticatorId;
    });
};

const removeVirtualAuthenticator = (client, authenticatorId) => {
  client
    .send("WebAuthn.removeVirtualAuthenticator", {
      authenticatorId,
    })
    .then((result) => {
      console.log("WebAuthn.removeVirtualAuthenticator", result);
    });
};

const addCredential = (client, authenticatorId) => {
  client
    .send("WebAuthn.addCredential", {
      authenticatorId,
      credential: {
        credentialId: CREDENTIAL_ID,
        isResidentCredential: true,
        rpId: "example.com",
        privateKey: ``,
        userHandle: "",
        signCount: Math.round(
          (new Date().getTime() - 1704444610871) / 1000 / 2
        ),
        // signCount: 2,
      },
    })
    .then((result) => {
      console.log("WebAuthn.addCredential", result);
    });
};

const removeCredential = (client, { authenticatorId, credentialId }) => {
  client
    .send("WebAuthn.removeCredential", {
      authenticatorId,
      credentialId,
    })
    .then((result) => {
      console.log("WebAuthn.removeCredential", result);
    });
};

const getCredentials = (client, authenticatorId) => {
  client
    .send("WebAuthn.getCredentials", {
      authenticatorId,
    })
    .then((result) => {
      console.log("WebAuthn.getCredentials", result);
    });
};

module.exports = {
  enableWebAuthn,
  addVirtualAuthenticator,
  removeVirtualAuthenticator,
  addCredential,
  removeCredential,
  getCredentials,
};

Hope this help. I use CDP to create passkeys. https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/