Web Authentication API
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2021.
Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.
The Web Authentication API (WebAuthn) is an extension of the Credential Management API that enables strong authentication with public key cryptography, enabling passwordless authentication and secure multi-factor authentication (MFA) without SMS texts.
Note: Passkeys are a significant use case for web authentication; see Create a passkey for passwordless logins and Sign in with a passkey through form autofill for implementation details. See also Google Identity > Passwordless login with passkeys.
WebAuthn concepts and usage
WebAuthn uses asymmetric (public-key) cryptography instead of passwords or SMS texts for registering, authenticating, and multi-factor authentication with websites. This has some benefits:
- Protection against phishing: An attacker who creates a fake login website can't login as the user because the signature changes with the origin of the website.
- Reduced impact of data breaches: Developers don't need to hash the public key, and if an attacker gets access to the public key used to verify the authentication, it can't authenticate because it needs the private key.
- Invulnerable to password attacks: Some users might reuse passwords, and an attacker may obtain the user's password for another website (e.g. via a data breach). Also, text passwords are much easier to brute-force than a digital signature.
Many websites already have pages that allow users to register new accounts or sign into an existing account, and WebAuthn acts as a replacement or enhancement for the authentication part of the system. It extends the Credential Management API, abstracting communication between the user agent and an authenticator and providing the following new functionality:
- When
navigator.credentials.create()
is used with thepublicKey
option, the user agent creates new credentials via an authenticator — either for registering a new account or for associating a new asymmetric key pair with an existing account.- When registering a new account, these credentials are stored on a server (also referred to as a service or a relying party) and can be subsequently used to log a user in.
- The asymmetric key pair is stored in the authenticator, which can then be used to authenticate a user with a relying party for example during MFA. The authenticator may be embedded into the user agent, into an operating system, such as Windows Hello, or it may be a physical token, such as a USB or Bluetooth Security Key.
- When
navigator.credentials.get()
is used with thepublicKey
option, the user agent uses an existing set of credentials to authenticate to a relying party (either as the primary login or to provide an additional factor during MFA as described above).
In their most basic forms, both create()
and get()
receive a very large random number called the "challenge" from the server and return the challenge signed by the private key back to the server. This proves to the server that a user has the private key required for authentication without revealing any secrets over the network.
Note: The "challenge" must be a buffer of random information at least 16 bytes in size.
Creating a key pair and registering a user
To illustrate how the credential creation process works, let's describe the typical flow that occurs when a user wants to register a credential to a relying party:
-
The relying party server sends user and relying party information to the web app handling the registration process, along with the "challenge", using an appropriate secure mechanism (for example Fetch or XMLHttpRequest).
Note: The format for sharing information between the relying party server and the web app is up to the application. A recommended approach is to exchange JSON type representation objects for credentials and credential options. Convenience methods have been created in
PublicKeyCredential
for converting from the JSON representations to the form required by the authentication APIs:parseCreationOptionsFromJSON()
,parseRequestOptionsFromJSON()
andPublicKeyCredential.toJSON()
. -
The web app initiates generation of a new credential via the authenticator, on behalf of the relying party, via a
navigator.credentials.create()
call. This call is passed apublicKey
option specifying device capabilities, e.g., whether the device provides its own user authentication (for example with biometrics).A typical
create()
call might look like so:jslet credential = await navigator.credentials.create({ publicKey: { challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]), rp: { id: "acme.com", name: "ACME Corporation" }, user: { id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]), name: "jamiedoe", displayName: "Jamie Doe" }, pubKeyCredParams: [ {type: "public-key", alg: -7} ] } });
The parameters of the
create()
call are passed to the authenticator, along with a SHA-256 hash that is signed to ensure that it isn't tampered with. -
After the authenticator obtains user consent, it generates a key pair and returns the public key and optional signed attestation to the web app. This is provided when the
Promise
returned by thecreate()
call fulfills, in the form of aPublicKeyCredential
object instance (thePublicKeyCredential.response
property contains the attestation information). -
The web app forwards the
PublicKeyCredential
to the server, again using an appropriate mechanism. -
The server stores the public key, coupled with the user identity, to remember the credential for future authentications. During this process, it performs a series of checks to ensure that the registration was complete and not tampered with. These include:
- Verifying that the challenge is the same as the challenge that was sent.
- Ensuring that the origin was the origin expected.
- Validating that the signature and attestation are using the correct certificate chain for the specific model of the authenticator used to generated the key par in the first place.
Warning: Attestation provides a way for a relying party to determine the provenance of an authenticator. Relying parties should not attempt to maintain allowlists of authenticators.
Authenticating a user
After a user has registered with WebAuthn, they can authenticate (i.e., login) with the service. The authentication flow looks similar to the registration flow, the main differences being that authentication:
- Doesn't require user or relying party information
- Creates an assertion using the previously-generated key pair for the service, rather than the authenticator's key pair.
A typical authentication flow is as follows:
-
The relying party generates a "challenge" and sends it to the user agent using an appropriate secure mechanism, along with a list of relying party and user credentials. It can also indicate where to look for the credential, e.g., on a local built-in authenticator, or on an external one over USB, BLE, etc.
-
The browser asks the authenticator to sign the challenge via a
navigator.credentials.get()
call, which is passed the credentials in apublicKey
option.A typical
get()
call might look like so:jslet credential = await navigator.credentials.get({ publicKey: { challenge: new Uint8Array([139, 66, 181, 87, 7, 203, ...]), rpId: "acme.com", allowCredentials: [{ type: "public-key", id: new Uint8Array([64, 66, 25, 78, 168, 226, 174, ...]) }], userVerification: "required", } });
The parameters of the
get()
call are passed to the authenticator to handle the authentication. -
If the authenticator contains one of the given credentials and is able to successfully sign the challenge, it returns a signed assertion to the web app after receiving user consent. This is provided when the
Promise
returned by theget()
call fulfills, in the form of aPublicKeyCredential
object instance (thePublicKeyCredential.response
property contains the assertion information). -
The web app forwards the signed assertion to the relying party server for the relying party to validate. The validation checks include:
- Using the public key that was stored during the registration request to validate the signature by the authenticator.
- Ensuring that the challenge that was signed by the authenticator matches the challenge that was generated by the server.
- Checking that the Relying Party ID is the one expected for this service.
-
Once verified by the server, the authentication flow is considered successful.
Controlling access to the API
The availability of WebAuthn can be controlled using a Permissions Policy, specifying two directives in particular:
publickey-credentials-create
: Controls the availability ofnavigator.credentials.create()
with thepublicKey
option.publickey-credentials-get
: Controls the availability ofnavigator.credentials.get()
with thepublicKey
option.
Both directives have a default allowlist value of "self"
, meaning that by default these methods can be used in top-level document contexts.
In addition, get()
can be used in nested browsing contexts loaded from the same origin as the top-most document.
get()
and create()
can be used in nested browsing contexts loaded from the different origins to the top-most document (i.e. in cross-origin <iframes>
), if allowed by the publickey-credentials-get
and publickey-credentials-create
Permission-Policy
directives, respectively.
For cross-origin create()
calls, where the permission was granted by allow=
on an iframe, the frame must also have Transient activation.
Note: Where a policy forbids use of these methods, the promises returned by them will reject with a NotAllowedError
DOMException
.
Basic access control
If you wish to allow access to a specific subdomain only, you could provide it like this:
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://subdomain.example.com")
Allowing embedded create
and get()
calls in an <iframe>
If you wish to authenticate with get()
or create()
in an <iframe>
, there are a couple of steps to follow:
-
The site embedding the relying party site must provide permission via an
allow
attribute:-
If using
get()
:html<iframe src="https://auth.provider.com" allow="publickey-credentials-get *"> </iframe>
-
If using
create()
:html<iframe src="https://auth.provider.com" allow="publickey-credentials-create 'self' https://a.auth.provider.com https://b.auth.provider.com"> </iframe>
The
<iframe>
must also have Transient activation ifcreate()
is called cross-origin.
-
-
The relying party site must provide permission for the above access via a
Permissions-Policy
header:httpPermissions-Policy: publickey-credentials-get=* Permissions-Policy: publickey-credentials-create=*
Or to allow only a specific URL to embed the relying party site in an
<iframe>
:httpPermissions-Policy: publickey-credentials-get=("https://subdomain.example.com") Permissions-Policy: publickey-credentials-create=("https://*.auth.provider.com")
Interfaces
AuthenticatorAssertionResponse
-
Provides proof to a service that an authenticator has the necessary key pair to successfully handle an authentication request initiated by a
CredentialsContainer.get()
call. Available in theresponse
property of thePublicKeyCredential
instance obtained when theget()
Promise
fulfills. AuthenticatorAttestationResponse
-
The result of a WebAuthn credential registration (i.e., a
CredentialsContainer.create()
call). It contains information about the credential that the server needs to perform WebAuthn assertions, such as its credential ID and public key. Available in theresponse
property of thePublicKeyCredential
instance obtained when thecreate()
Promise
fulfills. AuthenticatorResponse
-
The base interface for
AuthenticatorAttestationResponse
andAuthenticatorAssertionResponse
. PublicKeyCredential
-
Provides information about a public key / private key pair, which is a credential for logging in to a service using an un-phishable and data-breach resistant asymmetric key pair instead of a password. Obtained when the
Promise
returned via acreate()
orget()
call fulfills.
Extensions to other interfaces
CredentialsContainer.create()
, thepublicKey
option-
Calling
create()
with apublicKey
option initiates the creation of new asymmetric key credentials via an authenticator, as explained above. CredentialsContainer.get()
, thepublicKey
option-
Calling
get()
with apublicKey
option instructs the user agent uses an existing set of credentials to authenticate to a relying party.
Examples
Demo sites
- Mozilla Demo website and its source code.
- Google Demo website and its source code.
- WebAuthn.io demo website and its source code.
- github.com/webauthn-open-source and its client source code and server source code
Usage example
Note: For security reasons, the Web Authentication API calls (create()
and get()
) are canceled if the browser window loses focus while the call is pending.
// sample arguments for registration
const createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. - Service):
rp: {
name: "Acme",
},
// User:
user: {
id: new Uint8Array(16),
name: "[email protected]",
displayName: "Carina P. Anand",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
],
attestation: "direct",
timeout: 60000,
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x8c, 0x0a, 0x26, 0xff, 0x22, 0x91, 0xc1, 0xe9, 0xb9, 0x4e, 0x2e, 0x17,
0x1a, 0x98, 0x6a, 0x73, 0x71, 0x9d, 0x43, 0x48, 0xd5, 0xa7, 0x6a, 0x15,
0x7e, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0f, 0xef,
]).buffer,
},
};
// sample arguments for login
const getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xda, 0xee, 0xee, 0xb9, 0x94, 0xc3, 0xc2, 0x15,
0x67, 0x65, 0x26, 0x22, 0xe3, 0xf3, 0xab, 0x3b, 0x78, 0x2e, 0xd5, 0x6f,
0x81, 0x26, 0xe2, 0xa6, 0x01, 0x7d, 0x74, 0x50,
]).buffer,
},
};
// register / create a new credential
navigator.credentials
.create(createCredentialDefaultArgs)
.then((cred) => {
console.log("NEW CREDENTIAL", cred);
// normally the credential IDs available for an account would come from a server
// but we can just copy them from above…
const idList = [
{
id: cred.rawId,
transports: ["usb", "nfc", "ble"],
type: "public-key",
},
];
getCredentialDefaultArgs.publicKey.allowCredentials = idList;
return navigator.credentials.get(getCredentialDefaultArgs);
})
.then((assertion) => {
console.log("ASSERTION", assertion);
})
.catch((err) => {
console.log("ERROR", err);
});
Specifications
Specification |
---|
Web Authentication: An API for accessing Public Key Credentials - Level 3 # iface-pkcredential |
Browser compatibility
BCD tables only load in the browser