Skip to main content

@simplewebauthn/browser

Installation

npm

This package is available on npm for use in projects that contain a build step, like in a single-page application:

npm install @simplewebauthn/browser

It can then be imported as usual:

import SimpleWebAuthnBrowser from '@simplewebauthn/browser';

UMD

The Browser package can also be installed using a traditional <script> tag. Copy-paste the code below into your HTML page's <head> element to begin:

<script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>

NOTE: If using this in production, we recommend that you...

  1. Visit the URL in the above <script> tag to get the exact-version URL that it redirects you to.
  2. Enter that versioned URL into the SRI Hash Generator to create a version of that script tag that includes a subresource integrity checksum, to ensure you are always getting the exact, unmodified version of that file that you requested.

The library's methods will be available on the global SimpleWebAuthnBrowser object.

Methods

The following methods are exported from @simplewebauthn/browser:

startRegistration()

"Registration" is analogous to new account creation. The front end uses the following methods from this package to accomplish this:

import { startRegistration } from '@simplewebauthn/browser';

The front end's primary job during registration is the following:

  1. Get registration options from the Relying Party (your server)
  2. Submit registration options to the authenticator
  3. Submit the authenticator's response to the Relying Party for verification

Below is all of the front end JavaScript needed to fulfill these three steps using this package:

info

The code below is a basic implementation written in plain JavaScript for placement in a plain HTML document. @simplewebauthn/browser is installed following the "UMD" installation method mentioned above.

That said, this general sequence of events should be easily adaptable to the front end framework of your choice (React/VueJS/Svelte/etc...) for use in projects that follow the above npm install installation method.

<script>
const { startRegistration } = SimpleWebAuthnBrowser;

// <button>
const elemBegin = document.getElementById('btnBegin');
// <span>/<p>/etc...
const elemSuccess = document.getElementById('success');
// <span>/<p>/etc...
const elemError = document.getElementById('error');

// Start registration when the user clicks a button
elemBegin.addEventListener('click', async () => {
// Reset success/error messages
elemSuccess.innerHTML = '';
elemError.innerHTML = '';

// GET registration options from the endpoint that calls
// @simplewebauthn/server -> generateRegistrationOptions()
const resp = await fetch('/generate-registration-options');

let attResp;
try {
// Pass the options to the authenticator and wait for a response
attResp = await startRegistration(await resp.json());
} catch (error) {
// Some basic error handling
if (error.name === 'InvalidStateError') {
elemError.innerText = 'Error: Authenticator was probably already registered by user';
} else {
elemError.innerText = error;
}

throw error;
}

// POST the response to the endpoint that calls
// @simplewebauthn/server -> verifyRegistrationResponse()
const verificationResp = await fetch('/verify-registration', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(attResp),
});

// Wait for the results of verification
const verificationJSON = await verificationResp.json();

// Show UI appropriate for the `verified` status
if (verificationJSON && verificationJSON.verified) {
elemSuccess.innerHTML = 'Success!';
} else {
elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify(
verificationJSON,
)}</pre>`;
}
});
</script>

startAuthentication()

"Authentication" is analogous to existing account login. Authentication in the front end uses the following methods from this package:

import { startAuthentication } from '@simplewebauthn/browser';

The front end's primary job during authentication is the following:

  1. Get authentication options from the Relying Party (your server)
  2. Submit authentication options to the authenticator
  3. Submit the authenticator's response to the Relying Party for verification

Below is all of the front end JavaScript that is needed to fulfill these three steps using this package:

info

The code below is a basic implementation written in plain JavaScript for placement in a plain HTML document. @simplewebauthn/browser is installed following the "UMD" installation method mentioned above.

That said, this general sequence of events should be easily adaptable to the front end framework of your choice (React/VueJS/Svelte/etc...) for use in projects that follow the above "npm" installation method.

<script>
const { startAuthentication } = SimpleWebAuthnBrowser;

// <button>
const elemBegin = document.getElementById('btnBegin');
// <span>/<p>/etc...
const elemSuccess = document.getElementById('success');
// <span>/<p>/etc...
const elemError = document.getElementById('error');

// Start authentication when the user clicks a button
elemBegin.addEventListener('click', async () => {
// Reset success/error messages
elemSuccess.innerHTML = '';
elemError.innerHTML = '';

// GET authentication options from the endpoint that calls
// @simplewebauthn/server -> generateAuthenticationOptions()
const resp = await fetch('/generate-authentication-options');

let asseResp;
try {
// Pass the options to the authenticator and wait for a response
asseResp = await startAuthentication(await resp.json());
} catch (error) {
// Some basic error handling
elemError.innerText = error;
throw error;
}

// POST the response to the endpoint that calls
// @simplewebauthn/server -> verifyAuthenticationResponse()
const verificationResp = await fetch('/verify-authentication', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(asseResp),
});

// Wait for the results of verification
const verificationJSON = await verificationResp.json();

// Show UI appropriate for the `verified` status
if (verificationJSON && verificationJSON.verified) {
elemSuccess.innerHTML = 'Success!';
} else {
elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify(
verificationJSON,
)}</pre>`;
}
});
</script>

Browser autofill, a.k.a. Conditional UI

A second useBrowserAutofill argument can be set to true to set up support for credential selection via the browser's native autofill popup:

<head>
<!-- ... -->
<script>
const { startAuthentication } = SimpleWebAuthnBrowser;

fetch('/generate-authentication-options')
.then((options) => {
// Note the `true` argument here
startAuthentication(options, true)
.then(authResp => sendToServerForVerificationAndLogin)
.catch(err => handleError);
});
</script>
</head>
<body>
<!-- ... -->
<label for="username">Username</label>
<input type="text" name="username" autocomplete="webauthn">
info

The "webauthn" value in the autocomplete attribute is required for autofill to work. startAuthentication() will error out if an <input> with this autocomplete value cannot be found on the page.

"webauthn" can be combined with other typical autocomplete values, including "username" and "current-password", but must appear at the end to consistently trigger conditional UI across browsers. The following are all valid:

autocomplete="webauthn"
autocomplete="username webauthn"
autocomplete="current-password webauthn"
caution

Guidance from platform vendors indicates that it is important to initialize WebAuthn's Conditional UI experience as soon as possible. Placing this logic in <head> should give browsers enough time to query authenticators for any discoverable credentials to display to the user. It may also work to delay painting UI For N milliseconds to achieve the same thing in something like a single-page app that would trigger this experience some time after page load. It's still early days for this capability, though, and different browsers may have different quirks while the feature evolves.

This new "pending" WebAuthn request will be automatically cancelled on a subsequent execution of startAuthentication() in, for example, a click handler that triggers the browser's typical "modal" WebAuthn experience.

Conditional UI is still a nascent capability, but this method should be pretty reliable since the API is largely settled. This new logic has been successfully tested in both Chrome Canary v103 and Safari 16.0 in the macOS Ventura beta.

When supported, users can quickly authenticate by selecting a credential when interacting with the <input>:

Chrome Canary v103:

Conditional UI prompt in Chrome Canary v103

Safari 16.0:

Conditional UI prompt in Chrome Canary v103

The selection of a credential from this popup will resolve the Promise returned by startAuthentication(), at which point the response can be submitted to the server for verification like usual. If verification succeeds, then the user can be logged in.

browserSupportsWebAuthn()

This helper method is included in this package to help preemptively check for the browser's ability to make WebAuthn API calls:

import { browserSupportsWebAuthn } from '@simplewebauthn/browser';

startRegistration() and startAuthentication() both call this method internally. In some scenarios, though, it may be more desirable to hide UI when the page loads and a call to browserSupportsWebAuthn() returns false:

<script>
const elemBegin = document.getElementById('btnBegin');
const elemSuccess = document.getElementById('success');
const elemError = document.getElementById('error');

const { browserSupportsWebAuthn } = SimpleWebAuthnBrowser;

if (!browserSupportsWebAuthn()) {
elemBegin.style.display = 'none';
elemError.innerText = 'It seems this browser does not support WebAuthn...';
return;
}

// ...snip...
</script>

browserSupportsWebAuthnAutofill()

This helper method checks for a browser's support for "Conditional UI". When this feature is present, the browser is capable of presenting a list of the user's discoverable credentials in the browser's native autofill prompt.

import { browserSupportsWebAuthnAutofill } from '@simplewebauthn/browser';

This method is automatically called by startAuthentication() when true is passed as a second argument, but it may still be independently useful in, for example, single-page applications that may seek to initiate authentication after page load.

platformAuthenticatorIsAvailable()

"Platform authenticators" are known by most users by their brand names: Touch ID, Face ID, Windows Hello...this asynchronous method helps you identify opportunities in which these types of authenticators can be used by your users:

import { platformAuthenticatorIsAvailable } from '@simplewebauthn/browser';

These advanced types of authenticators are typically embedded into a user's computer or phone and offer quick confirmation of a user's identity via biometric scan or PIN fallback. As a result of their convenience, you may wish to prioritize user registration of such authenticators when one is available for use:

<script>
const { platformAuthenticatorIsAvailable } = SimpleWebAuthnBrowser;

(async () => {
if (await platformAuthenticatorIsAvailable()) {
/**
* Prompt the user to use Touch ID/Windows Hello/etc... or security keys to register or
* authenticate
*
* How to decide which name to show for the device's platform authenticator is an exercise
* left up to the developer (your best bet is User Agent analysis)
*/
} else {
/**
* Only prompt the user to use security keys to register or authenticate
*/
}
})();
</script>

Helpers

base64URLStringToBuffer()

This method helps convert a base64url-encoded string into a Uint8Array:

import { base64URLStringToBuffer } from '@simplewebauthn/browser';

This method is not typically needed when using this library, but has been made available nonetheless for those who may need it.

bufferToBase64URLString()

This method helps convert a Uint8Array into a base64url-encoded string:

import { bufferToBase64URLString } from '@simplewebauthn/browser';

This method is not typically needed when using this library, but is available nonetheless for those who may find utility in such a method.

Troubleshooting

startRegistration() unexpectedly errors out with NowAllowedError after scanning QR code

When a call to startRegistration() results in scanning a QR code with a mobile device, scanning the QR code displayed in Google Chrome may result in a NotAllowedError in the browser and an unexpected error on the mobile device:

NotAllowedError: The operation either timed out or was not allowed. See: https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client.
at new r (index.es5.umd.min.js:2:3304)
at index.es5.umd.min.js:2:8932
at index.es5.umd.min.js:2:10161
at index.es5.umd.min.js:2:1934
at Object.throw (index.es5.umd.min.js:2:2039)
at s (index.es5.umd.min.js:2:808)
Caused by: DOMException: The operation either timed out or was not allowed. See: https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client.

This can be caused by an empty value string value (i.e. "") for user.displayname in the options passed to startRegistration().

To fix this, set user.displayName to the same value as user.name and try again. If you are using @simplewebauthn/servers's generateRegistrationOptions() method then you can set the userDisplayName argument to the same value as the userName argument to achieve the same result.

This is confirmed Chrome-specific bug that affects Chrome up through 127. A bug report for this issue has been logged as https://issues.chromium.org/issues/346835891. These docs will be updated when this issue is resolved and a new version of Chrome improves this behavior.