Package com.yubico.webauthn
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. 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:
-
Store the
keyId
andpublicKeyCose
as a new credential for the user. TheCredentialRepository
will need to look these up for authentication. -
Inspect the
warnings
- ideally there should of course be none. -
If you care about authenticator attestation, use the
attestationTrusted
,attestationType
andattestationMetadata
fields to enforce your attestation policy. -
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
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:
-
Use the
username
and/oruserHandle
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 thesignatureCount
result. -
Inspect the
warnings
- ideally there should of course be none.
-
Interface Summary Interface Description CredentialRepository An abstraction of the database lookups needed by this library.