webauthn-ui is a browser JS library that functions as a translator between the W3C Web Authentication API that modern browsers support and a json compatible version that can be send through form posts or XHR calls.
It can also handle some auxilliary tasks such as initiating the WebAuthn requests, detecting support and posting the response in a hidden form field.
- API for translating between the WebAuthn API structures and pure json structures.
- Automatically initiating a WebAuthn request from a button click and posting the response as json in a hidden form field without the need for custom javascript.
- Detection of WebAuthn support with graceful error handling.
- Adding CSS classes indicating WebAuthn feature support.
The WebAuthn API makes use of numerous JavaScript structures (such as PublicKeyCredential) for its inputs and outputs. For the most part these structures contain simple values that can directly be converted to json. However, binary ArrayBuffers are used in several places and these need to be converted back and forth to another type (base64url encoded strings) when translating to and from json.
This library will keep the types supported in json the same. ArrayBuffers are converter to base64url encoded strings. To convert json back to the JavaScript structures knowledge of the structure is needed because base64url encoded strings are indistinguishable from normal strings. For this reason webauthn-ui only supports the fields that it knows of.
For convenience a custom html5 data attribute can be used to automatically initiate a WebAuthn request on a given event and posting the response to a hidden form input element. Using this method no custom javascript is needed, the webauthn-ui library can be included and it will automatically look for a json configuration in either a data-webauthn
attribute on an input field or a <script type='application/json' data-webauthn>
block containing the configuration.
Install the module with npm install webauthn-ui
.
The library is available as ES and UMD modules. Both libraries are ES5 compatible, but do require a Promise implementation. For IE11 this means you need to use a polyfill such as es6-promise. Although all browsers that support WebAuthn support ES modules as well, it is still useful to support ES5 browsers for graceful handling in case WebAuthn is not supported.
You can use the ES module with the import
statement in your main code.
// When using automatic loading via json scripts (see below), just importing the library is enough:
import 'webauthn-ui';
// Or if you need access to the WebAuthnUI class:
import { WebAuthnUI } from 'webauthn-ui';
The UMD module can be used directly as a browser script (exposing WebAuthnUI as a global variable) or using CommonJS style require
As a script:
<!-- es6-promise for old and crappy browsers -->
<script type="application/javascript" src="es6-promise.auto.min.js"></script>
<script type="application/javascript" src="webauthn-ui.min.js"></script>
Or using require:
// For old and crappy browsers
require('es6-promise')
// When using automatic loading via json scripts/html attributes (see below), just importing the library is enough:
require('webauthn-ui');
// Or if you need access to the WebAuthnUI class:
const WebAuthnUI = require('webauthn-ui').WebAuthnUI;
The easiest way to use the library is to add a data-webauthn
to an input element with a json configuration as the attribute's value. When the page is loaded it will automatically setup an event handler for a specified element (e.g. a button to click) to initiate the WebAuthn request specified in the json data without the need for any custom JavaScript. The response will be put in the field's value and the form submitted automatically (by default).
Example WebAuthn registration:
<form method="post">
<!-- other form data -->
<!-- trigger button -->
<button type="button" id="register-btn">Register</button>
<input type="hidden" name="response"
data-webauthn="{"type":"create","trigger":{"event":"click","element":"#register-btn"},"request":{"rp":{"name":"WebAuthn demo"},"user":{"name":"freddy","id":"cGJ6WVlzRXNF","displayName":"Freddy fruitcake"},"challenge":"7b1m6n2KgMAuTp-FbOAl6sb0gD_5HZITqDF7ld8tl28","pubKeyCredParams":[{"type":"public-key","alg":-7}]}}"
/>
</form>
As an alternative you can also add the data-webauthn
to a json script tag. Specify a form field to contain the response using a CSS selector in the formField
property.
Example WebAuthn registration:
⚠️ Warning While using json inside a<script>
tag does not require HTML escaping of characters like '<', you will need to make sure the content cannot break out the script using</script>
in the content and that user input is safely encoded in the json data (no escaping the string values when it contains"
for example). Use a proper json encoding function or library and make sure it escapes/
characters as\/
to make it impossible for the json to contain a</script>
closing tag.
Note that for examplejson.stringify
in does not do this by default and is unsafe to use for this purpose. Use a library likeserialize-javascript
for example. PHP'sjson_encode
does safely encode content by default.
<script type="application/json" data-webauthn>
{
"formField": "#webauthn-response",
"type": "create",
"trigger": {
"event": "click",
"element": "#register-btn"
},
"request": {
"rp": {
"name": "WebAuthn demo"
},
"user": {
"name": "freddy",
"id": "cGJ6WVlzRXNF",
"displayName": "Freddy fruitcake"
},
"challenge": "7b1m6n2KgMAuTp-FbOAl6sb0gD_5HZITqDF7ld8tl28",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
]
}
}
</script>
<form method="post">
<!-- other form data -->
<button type="button" id="register-btn">Register</button>
<input type="hidden" id="webauthn-response" name="response" />
</form>
Both methods will wait until the user clicks the button, ask the client to create a new credential and posts the result (both on success and failure) as a serialized json string in the hidden form input.
On success, the json structure will be an object with a status
field set to the string "ok"
, and a "credential"
field
set to the PublicKeyCredential result of the WebAuthn request. The structure
of this object is the same as defined in the WebAuthn standard, but with all binary ArrayBuffers converted to base64url
encoded strings. An example of a successful response looks like this (values are shortened for display purposes):
{
"status": "ok",
"credential": {
"type": "public-key",
"id": "HlmkAY_zkPN0_ZWdlvOKjrJsYrBC-WeqV2vQayJQRVD4JvA7ttK4Zv4ivRMM3B8273Gt_bOcDTCIY_HIHdXQ_Q",
"rawId": "HlmkAY_zkPN0_ZWdlvOKjrJsYrBC-WeqV2vQayJQRVD4JvA7ttK4Zv4ivRMM3B8273Gt_bOcDTCIY_HIHdXQ_Q",
"response": {
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiI3YjFtNm4yS2dNQ......IsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ",
"attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0...DvLFRA5Bn3dGgzy"
}
}
}
In case of failure, the result will be an object with the status
field set to the string "failed"
and an error
field set to a string indicating the type of error. For example:
{
"status": "failed",
"error": "dom-not-allowed"
}
Field | Type | Required | Meaning |
---|---|---|---|
type | 'create'|'get' | Yes | The type of credential request to perform: create or get a credential. |
request | object | Yes | The request options for create/get credential as specified in the WebAuthn standard but with ArrayBuffers converted to base64url strings. |
trigger | {type:'click', element: 'selector'|Element} | Yes | Specify trigger to initiate the request. |
formField | string selector or DOM element | Yes/No | The input field to set save the result in. Not required if data-webauthn attribute is set on an input element. Can also be a textarea for debugging (in combination with submitForm = false) |
postUnsupportedImmediately | boolean (default false) | No | When WebAuthn is unsupported by the client, post an 'unsupported' error response immediately without user interaction |
submitForm | boolean (default true) | No | Submit form after setting the input field to the response. When false, only the input field is set but the form is not submitted. |
Automatic mode is useful if you want to write minimal code and a form post with the response is suitable for your setup. If you want more flexibility, have a single page web application or want to post the response using javascript you can manually call the library. It will stil take care of the ArrayBuffer <-> base64url conversion for you.
Use WebAuthnUI.createCredential
to create a credential, it works similar to navigator.credentials.create
. On error,
a WebAuthnUI
import { WebAuthnUI } from 'webauthn-ui';
let request = {
"rp": {
"name": "WebAuthn demo"
},
"user": {
"name": "freddy",
"id": "cGJ6WVlzRXNF",
"displayName": "Freddy fruitcake"
},
"challenge": "7b1m6n2KgMAuTp-FbOAl6sb0gD_5HZITqDF7ld8tl28",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
]
};
try {
let result = await WebAuthnUI.createCredential(request);
// Success
console.log(result);
} catch(error) {
// Failure
console.error(error);
}
To request a credential assertion, use WebAuthnUI.getCredential
in the same manner.
In case of failure the functions throw an error of type WebAuthnError (this class is exported by the module as well).
The name
field in this error indicates the error type. The possible error values are (might be extended in the future):
Error value | Type of error |
---|---|
unsupported | WebAuthn is not supported by client |
parse-error | Parse error (e.g. wrong base64url) |
bad-config | Configuration is incorrect |
dom-not-allowed | DOM NotAllowedError |
dom-security | DOM SecurityError |
dom-not-supported | DOM NotSupportedError |
dom-abort | DOM AbortError |
dom-invalid-state | DOM InvalidStateError |
dom-unknown | Other DOM error |
unknown | Unknown error |
The unsupported
error is returned by webauthn-ui if the client does not support WebAuthn. This allows graceful handling
of older browsers such as IE.
The innerError
field is set to the original error throw, if any.
When the CSS class webauthn-detect
is added to an element, this library will add additional classes to this element
indicating the degree of WebAuthn support:
General WebAuthn support:
webauthn-supported
webauthn-unsupported
User verifying platform authentiator support:
webauthn-uvpa-supported
webauthn-uvpa-unsupported
Example:
<div class='webauthn-detect'>...</div>
After library and DOM is loaded:
<div class='webauthn-detect webauthn-supported webauthn-uvpa-supported'>...</div>