See: Description
| Interface | Description |
|---|---|
| CredentialRepository |
An abstraction of the database lookups needed by this 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.
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.
At its core, the library provides four operations:
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.
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.
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.
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. You should store this in temporary storage so that it can later be passed as an argument to RelyingParty.finishRegistration(FinishRegistrationOptions).
After receiving the response from the client, construct a PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
from the response and wrap that 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:
keyId and publicKeyCose as a new credential for the user. The CredentialRepository will need to look these up for authentication.
warnings - ideally there should of course be
none.
attestationTrusted, attestationType and attestationMetadata fields to enforce your
attestation policy.
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 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 of the user to authenticate, but even this parameter is optional. If the username is not set, then the
allowCredentials parameter
will not be set. This which means the user must use a client-side-resident
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, store the AssertionRequest in temporary storage so it can be passed as an argument to RelyingParty.finishAssertion(com.yubico.webauthn.FinishAssertionOptions).
After receiving the response from the client, construct a PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
from the response and 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:
username and/or userHandle results to initiate a user session.
credentialId result) to equal the value returned in the signatureCount result.
warnings - ideally there should of course be
none.