To provide higher security for logins, websites are deploying two-factor authentication (2FA), often using a smartphone application or text messages. Those mechanisms make phishing harder but fail to prevent it entirely — users can still be tricked into passing along codes, and SMS messages can be intercepted in various ways.

Firefox 60 will ship with the WebAuthn API enabled by default, providing two-factor authentication built on public-key cryptography immune to phishing as we know it today. Read on for an introduction and learn how to secure millions of users already in possession of FIDO U2F USB tokens.

Creating a new credential

Let’s start with a simple example: this requests a new credential compatible with a standard USB-connected FIDO U2F device; there are many of these compliant tokens sold with names like Yubikey, U2F Zero, and others:

const cose_alg_ECDSA_w_SHA256 = -7; /* The challenge must be produced by the server */ let challenge = new Uint8Array([21,31,105 /* 29 more random bytes generated by the server */]); let pubKeyCredParams = [{ type: "public-key", alg: cose_alg_ECDSA_w_SHA256 }]; let rp = { name: "Test Website" }; let user = { name: "Firefox User <firefox@example.com>", displayName: "Firefox User", id: new TextEncoder("utf-8").encode("firefox@example.com") }; let publicKey = {challenge, pubKeyCredParams, rp, user}; navigator.credentials.create({publicKey}) .then(decodeCredential);

In the case of USB U2F tokens, this will make all compatible tokens connected to the user’s system wait for user interaction. As soon as the user touches any of the devices, it generates a new credential and the Promise resolves.

The user-defined function decodeCredential() will decode the response to receive a key handle, either a handle to the ECDSA key pair stored on the device or the ECDSA key pair itself, encrypted with a secret, device-specific key. The public key belonging to said pair is sent in the clear.

The key handle, the public key, and a signature must be verified by the backend using the random challenge. As a credential is cryptographically tied to the web site that requested it, this step would fail if the origins don’t match. This prevents reuse of credentials generated for other websites.

The key handle and public key will from now on be associated with the current user. The WebAuthn API mandates no browser UI, which means it’s the sole responsibility of the website to signal to users they should now connect and register a token.

Getting an assertion for an existing credential

The next time the user logs into the website they will be required to prove possession of the second factor that created the credential in the previous section. The backend will retrieve the key handle and send it with a new challenge to the user. As allowCredentials is an array, it allows sending more than one token, if multiple tokens are registered with a single user account.

/* The challenge must be produced by the server */ let challenge = new Uint8Array([42,42,33 /* 29 more random bytes generated by the server */]); let key = new Uint8Array(/* … retrieve key handle … */); let allowCredentials = [{ type: "public-key", id: key, transports: ["usb"] }]; let publicKey = {challenge, allowCredentials}; navigator.credentials.get({publicKey}) .then(decodeAssertion);

Again, all connected USB U2F tokens will wait for user interaction. When the user touches a token it will try to either find the stored key handle with the given ID, or try to decrypt it with the internal secret key. On success, it will return a signature. Otherwise the authentication flow will abort and will need to be retried by the website.

After decoding, the signature and the key handle that were used to sign are sent to the backend. If the public key stored with the key handle is able to verify the given signature over the provided challenge, the assertion is considered valid and the user will be logged in.

First Factor Authentication

Web Authentication also defines the mechanisms to log in without a username and password at all using a secure token － such as the trusted execution environment on your smartphone. In this mode, your token would attest that you not only have possession of it, but also that you, as a person, unlocked the token using a passcode (something you know) and/or a biometric (something you are).

In this world, websites could let you enroll to perform seamless authentication to a web application on your desktop by answering a prompt which appears on your smartphone.

The FIDO U2F tokens deployed today aren’t sophisticated enough to make this happen yet, but the next generation of tokens will be, and web developers will interact with those FIDO 2.0 tokens using Web Authentication.

WebAuthn, coming to a Firefox near you

This was a very short introduction to the world of Web Authentication and it intentionally omits a lot of nitty-gritty details such as CBOR encoding and COSE_Key formats, as well as further parameters that can be passed to the .create() and .get() functions.

We would like to encourage developers to start experimenting with WebAuthn, and allow users to secure their logins with second factors, as the API becomes available. We are not aware of any WebAuthn-U2F polyfill libraries at the time of writing, but hope that these will be available soon. If you have seen something promising, please let us know in the comments.

It is very exciting to bring standardized two-factor authentication to the web; public-key cryptography already protects our data as it travels the Internet via the TLS protocol, and now we can use it to make phishing a lot, lot harder. Give WebAuthn a try in Firefox Nightly!

A final note about testing

Web Authentication is a powerful feature. As such, it can only be used in Secure Contexts, and if used in a frame, only when all of the frames are from the same origin as the parent document. This means that you are likely to encounter security errors when experimenting with it on some popular testing websites (such as jsfiddle.net).