Package com.yubico.webauthn

This package and its subpackages make up the public API of the webauthn-server-core library.

The main entry point is the RelyingParty class. It provides methods for generating inputs to the navigator.credentials.create() and navigator.credentials.get() methods and for processing the return values from those same methods. In order to do this, the RelyingParty needs an instance of the CredentialRepository interface to use for looking up the credential IDs and public keys registered to each user, among other things.

What this library does not do

This library has no concept of accounts, sessions, permissions or identity federation - it only deals with executing the Web Authentication authentication mechanism. Sessions, account management and other higher level concepts can make use of this authentication mechanism, but the authentication mechanism alone does not make a security system.

Usage overview

At its core, the library provides four operations:

  • Initiate a registration operation given a user and some settings for the credential to be created
  • Finish a registration operation given the initiation request and the authenticator response
  • Initiate an authentication operation given a username
  • Finish an authentication operation given the initiation request and the authenticator response

The "start" methods return request objects containing the parameters to be used in the call to navigator.credentials.create() or navigator.credentials.get(), and the "finish" methods expect a pair of such a request object and the response object returned from the browser. The library itself is stateless; once constructed, a RelyingParty instance never modifies its fields, and the "finish" methods return plain object representations of the results. These methods perform all the verification logic specified by Web Authentication, but it is your responsibility as the library user to store pending requests and act upon the returned results - including enforcing policies and updating databases.

Data classes and builders

Logic classes as well as data classes in this library are all immutable, and provide builders for their construction. Most builders have required parameters, which is encoded in the type system - the build() method will be made available only once all required parameters have been set. The data classes also each have a toBuilder() method which can be used to create a modified copy of the instance.

Instantiating the library

The main entry point to the library is the RelyingParty class, which can be instantiated via its builder. Refer to the RelyingParty.RelyingPartyBuilder documentation for descriptions of the parameters. Of particular note is the credentialRepository parameter, which takes an application-specific database adapter to use for looking up users' credentials. You'll need to implement the CredentialRepository interface with your own database access logic.

Like all other classes in the library, RelyingParty is stateless and therefore thread safe.

Registration

To initiate a registration operation, construct a StartRegistrationOptions instance using its builder and pass that into RelyingParty.startRegistration(StartRegistrationOptions). The only required parameter is a UserIdentity describing the user for which to create a credential. One noteworthy part of UserIdentity is the id field, containing the user handle for the user. This should be a stable, unique identifier for the user - equivalent to a username, in most cases. However, due to privacy considerations it is recommended to set the user handle to a random byte array rather than, say, the username encoded in UTF-8.

The startRegistration method returns a PublicKeyCredentialCreationOptions which can be serialized to JSON and passed as the publicKey argument to navigator.credentials.create(). You can use the toBuilder() method to make any modifications you need, then the PublicKeyCredentialCreationOptions.toCredentialsCreateJson() method is suitable for converting the value to JSON to send to the client.

You should also store the PublicKeyCredentialCreationOptions object in temporary storage so that it can later be passed as an argument to RelyingParty.finishRegistration(FinishRegistrationOptions). If you need to serialize the object for storage, the PublicKeyCredentialCreationOptions.toJson() and PublicKeyCredentialCreationOptions.fromJson(java.lang.String) methods are suitable for serializing to and from a string value.

After receiving the response from the client, use the PublicKeyCredential.parseRegistrationResponseJson(java.lang.String) function to parse the response and wrap it in a FinishRegistrationOptions along with the PublicKeyCredentialCreationOptions used to initiate the request. Pass that as the argument to RelyingParty.finishRegistration(FinishRegistrationOptions), which will return a RegistrationResult if successful and throw an exception if not. Regardless of whether it succeeds, you should remove the PublicKeyCredentialCreationOptions from the pending requests storage to prevent retries.

Finally, use the RegistrationResult to update any database(s) and take other actions depending on your application's needs. In particular:

  • Store the keyId and publicKeyCose as a new credential for the user. The CredentialRepository will need to look these up for authentication.
  • Store the signature counter value in the new credential. If available, this will be used in future authentication ceremonies do detect authenticator cloning.
  • Optionally, store the isDiscoverable flag, if present, in the new credential. This may help you determine which user interaction flows are possible with which credential.
  • If you care about authenticator attestation, check that the attestationTrusted field satisfies your attestation policy. For this you will likely need to configure the attestationTrustSource setting on your RelyingParty instance. See also the webauthn-server-attestation for an implementation of such an attestation trust and metadata source.
  • If you care about authenticator attestation, it is recommended to also store the raw attestation object as part of the credential. This enables you to retroactively inspect credential attestations in response to policy changes and/or compromised authenticators.

Authentication

Authentication works much like registration, except less complex because of the fewer parameters and the absence of authenticator attestation complications.

To initiate an authentication operation, call RelyingParty.startAssertion(StartAssertionOptions). The main parameter you need to set here is the username or user handle of the user to authenticate, but even these parameters are optional. If neither is set, then the allowCredentials parameter will not be set. This means the user must use a client-side-discoverable credential to authenticate; also known as "first-factor authentication". This use case has both advantages and disadvantages; see the Web Authentication specification for an extended discussion of this.

The startAssertion method returns an AssertionRequest containing the username, if any, and a PublicKeyCredentialRequestOptions instance which can be serialized to JSON and passed as the publicKey argument to navigator.credentials.get(). Again, use AssertionRequest.toBuilder() to make any necessary changes, AssertionRequest.toCredentialsGetJson() to convert it to JSON for sending to the client, and store the AssertionRequest in temporary storage so it can be passed as an argument to RelyingParty.finishAssertion(com.yubico.webauthn.FinishAssertionOptions). Again, AssertionRequest.toJson() and AssertionRequest.fromJson(java.lang.String) can be used to convert to and from JSON for storage.

After receiving the response from the client, use PublicKeyCredential.parseAssertionResponseJson(java.lang.String) to parse the response, then wrap that in a FinishAssertionOptions along with the AssertionRequest used to initiate the request. Pass that as the argument to RelyingParty.finishAssertion(com.yubico.webauthn.FinishAssertionOptions), which will return an AssertionResult if successful and throw an exception if not. Regardless of whether it succeeds, you should remove the AssertionRequest from the pending requests storage to prevent retries.

Finally, use the AssertionResult to update any database(s) and take other actions depending on your application's needs. In particular:

  • Use the username and/or userHandle results to initiate a user session.
  • Update the stored signature count for the credential (identified by the credentialId result) to equal the value returned in the signatureCount result.