Through WenAuthn the RelyingParty can also request the users permission to perform an authentication operation with an existing credential. This process is similar to registration however no user information is required and authentication assertions are signed with the key created during registration rather than the attestation certificate.
Authentication
In the authentication flow the RelyingParty constructs PublicKeyCredentialRequestOptions
and calls the WebAuthn navigator.credentials.get()
method.
In the FIDO component the FidoAuthentication
service can be injected into the controller and the InitiateAuthentication
method called before page load, the returned FidoAuthenticationChallenge
is used to supply the values for the PublicKeyCredentialRequestOptions
PublicKeyCredentialRequestOptions
- allowCredentials :
object
list of public key credentials acceptable to the RP. Credentials can be omitted in username-less authentication scenario. An entry includes the type of the credential and credential id, and optionally a list of transports the client can use to find the credential.
A credential is made up of id : string
the credential id returned by the RelyingParty and a type : string
the type of credential, in this case public-key
.
{
"publicKey": {
"allowCredentials": [
{
"id": "...",
"type": "public-key"
}
],
...
}
}
The InitiateAuthentication
method takes an optional username argument, if a username is supplied all the credential ids for that user are returned in the FidoAuthenticationChallenge
in a field called Base64KeyIds
. These keys can be used to construct the allowed credentials array.
-
challenge :
Uint8Array
contains a random value generated by the RP from the authenticator to sign as part of the authentication assertion. This value is supplied in theFidoAuthenticationChallenge
. -
rpId :
string
relying party identifier claimed by the caller. This must exactly match the rp.id specified during registration. This value is supplied in theFidoAuthenticationChallenge
-
timeout :
int
the time, in milliseconds, that the caller is willing to wait for the call to complete. -
UserVerification :
string
The user verification value can be set aspreferred
,required
ordiscouraged
. If no value is supplied, it will default topreferred
.discouraged
means the RelyingParty does not want user verification during the operation, this could be to minimize disruption to the user flow.preferred
means that the RelyingParty prefers user verification if possible but will not fail the if the response does have the AuthenticatorDataFlags.UV set.required
means the operation will fail if the response does have the AuthenticatorDataFlags.UV set. The operation will fail if the key does not support user verification and may fail depending on the browser if no PIN or biometric has been configured
Complete Authentication
Once the PublicKeyCredentialRequestOptions
has been constructed call the WebAuthn API navigator.credentials.get()
method with constructed credentials. The method returns an encoded credentials object. This data must then turned into JSON and posted to a endpoint (See code fragment below) to be passed to the FidoAuthentication.CompleteAuthentication
method.
// Create Uint8Array Challenge from challenge generated by the FIDO2 component
let challengeBytesAsString = atob("@Html.Raw(Model.Base64Challenge)");
let challenge = new Uint8Array(challengeBytesAsString.length);
for (let i = 0; i < challengeBytesAsString.length; i++) {
challenge[i] = challengeBytesAsString.charCodeAt(i);
}
// Create Allowed credentials array from KeyIds from FIDO2 component
var keys = JSON.parse('@Html.Raw(JsonConvert.SerializeObject(Model.Base64KeyIds))');
var allowCredentials = [];
for (let i = 0; i < keys.length; i++) {
let keyIdBytesAsString = window.atob(keys[i]);
let key = new Uint8Array(keyIdBytesAsString.length);
for (let i = 0; i < keyIdBytesAsString.length; i++) {
key[i] = keyIdBytesAsString.charCodeAt(i);
}
allowCredentials.push({
type: "public-key",
id: key
});
}
// Set RelyingParty details
var rpId = "@Model.RelyingPartyId";
var publicKey = { challenge, allowCredentials, rpId };
navigator.credentials.get({ publicKey }).then((result) => {
//Post WebAuthn Response to Fido Server for validation and attestation
var encodedCredentials = {
id: result.id,
rawId: btoa(String.fromCharCode.apply(null, new Uint8Array(result.rawId))),
type: result.type,
response: {
authenticatorData:
btoa(String.fromCharCode.apply(null, new Uint8Array(result.response.authenticatorData))),
signature:
btoa(String.fromCharCode.apply(null, new Uint8Array(result.response.signature))),
userHandle:
btoa(String.fromCharCode.apply(null, new Uint8Array(result.response.userHandle))),
clientDataJSON:
btoa(String.fromCharCode.apply(null, new Uint8Array(result.response.clientDataJSON)))
}
};
$.ajax({
url: '/Home/LoginCallback',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(encodedCredentials),
success:function() {
window.location.href = "/";
}
});
});
The FIDO component contains types to model FIDO payloads. To receive the encoded credential object from the browser use Base64FidoAuthenticationResponse
.
[HttpPost]
public async Task<IActionResult> LoginCallback([FromBody] Base64FidoAuthenticationResponse authenticationResponse)
{
IFidoAuthenticationResult result = await fido.CompleteAuthentication(authenticationResponse.ToFidoResponse());
if (result.IsSuccess)
{
await HttpContext.SignInAsync("cookie", new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim("sub", result.UserId)
}, "cookie")));
}
if (result.IsError) return BadRequest(result.ErrorDescription);
return Ok();
}
The FidoAuthentication.CompleteAuthentication
method requires a IFidoAuthenticationResponse
the ToFidoResponse
extension method generates a IFidoAuthenticationResponse
from the Base64FidoAuthenticationResponse
.
If the result is a success then the user can be signed into the default authentication scheme.