At this stage you have created your PublicKeyCredentialCreationOptions
, sent them to the client application, and now the client has returned a passkey that it wants registered for the user. This next method will demonstrate how to store this credential in your repository for later use during authentication ceremonies.
This step will validate that the credential follows the rules that were set by the PublicKeyCredentialCreationOptions
, adds the registration to the database, and will invalidate the registration request to prevent replay attacks or repeated registration attempts.
Figure 10 will demonstrate a method that will finalize a registration to store the passkey in a credential repository - Please note that some of the code leverages methods/class from the Yubico java-webauthn-server library.
Object finishRegistration(JsonObject responseJson) {
//Step 1
RegistrationResponse response;
try {
response = jsonMapper.readValue(responseJson.toString(), RegistrationResponse.class);
} catch (Exception e) {
return e;
}
//Step 2
RegistrationRequest request = registerRequestStorage.getIfPresent(response.getRequestId());
registerRequestStorage.invalidate(response.getRequestId());
if (request == null) {
String msg = "fail finishRegistration - no such registration in progress: {}" + response.getRequestId();
return new Exception(msg);
} else {
//Step 3
try {
com.yubico.webauthn.RegistrationResult registration = rp.finishRegistration(
FinishRegistrationOptions.builder()
.request(request.getPublicKeyCredentialCreationOptions())
.response(response.getCredential())
.build());
//Step 4
return addRegistration(
request.getPublicKeyCredentialCreationOptions().getUser(),
response,
registration,
request);
} catch (RegistrationFailedException e) {
return e;
} catch (Exception e) {
return e;
}
}
}
private CredentialRegistration addRegistration(
UserIdentity userIdentity,
RegistrationResponse response,
RegistrationResult result,
RegistrationRequest request) {
Optional<String> nickname = Optional.empty();
nickname = Optional.ofNullable("My Security Key");
return addRegistration(
userIdentity,
nickname,
response.getCredential().getResponse().getAttestation().getAuthenticatorData().getSignatureCounter(),
RegisteredCredential.builder()
.credentialId(result.getKeyId().getId())
.userHandle(userIdentity.getId())
.publicKeyCose(result.getPublicKeyCose())
.signatureCount(response.getCredential().getResponse().getParsedAuthenticatorData()
.getSignatureCounter())
.build(),
request);
}
private CredentialRegistration addRegistration(
UserIdentity userIdentity,
Optional<String> nickname,
long signatureCount,
RegisteredCredential credential,
RegistrationRequest request) {
CredentialRegistration reg = CredentialRegistration.builder()
.userIdentity(userIdentity)
.credentialNickname(nickname)
.registrationTime(clock.instant())
.lastUsedTime(clock.instant())
.lastUpdatedTime(clock.instant())
.credential(credential)
.signatureCount(signatureCount)
.registrationRequest(request)
.build();
userStorage.addRegistrationByUsername(userIdentity.getName(), reg);
return reg;
}
First we will format the response sent by the client application. This will ensure that it was formatted correctly for processing by our relying party. If the response is not in a standard format, then it’s possible that the browser or authenticator were not implemented correctly.
We will return an error if the response cannot be properly formatted.
Next we will ensure that this response was part of a valid registration request. Our relying party will not process just any passkey that is sent to it - it must match a request that it issued. If no request was found, then an error is returned. If there is a matching registration request, then we will invalidate it in the database so that it cannot be used again; for this reason if this registration fails, the user will need to reinvoke a new registration request
Next, we will use the java-webauthn-server library’s RelyingParty.FinishRegistration()
method to ensure that the credential is valid, and is able to correctly sign the initial challenge that was offered during registration.
Once the credential is finalized, we can add the registration to our credential repository.
This final step includes a call to a method addRegistration()
. This will take in some information that will be formatted for storage in the credential repository. The value in the credential repository will be stored as a CredentialRegistration
, which is a custom class that was developed for our example; what is important is that it can store a RegisteredCredential
object, defined by the java-webauth-server library. Overall our credential will be stored to include;
-
The identity of the user who owns it
-
Passkey nickname
-
Time based metadata such as last use, creation date, and last update
-
The passkey itself
-
Registration request that was used to create the passkey
Once the object has been formatted, we can store it in the credential repository.
Note, that this example did not cover how to use attestation to determine the make and model, and storing additional metadata along with the credential. This will be covered in the attestation guide in an upcoming page.
Next let’s understand how to use that passkey to authenticate into your account.