Test user authentication with the FIDO2 Sample Relying Party web application

You can use the FIDO2 Sample Relying Party web applicationClosed Stand-alone component that allows you to test and simulate the end-to-end capabilities of the FIDO2 registration and authentication flows. With this app, you can also deregister all the user keys from the FIDO2 Server. to test the user authentication flow.

Authentication flow code samples

The code samples in this article do NOT contain actual production code, rather, the examples used here serve to demonstrate how to integrate FIDO2 into your website.

Initialization

The user initializes the authentication by clicking the Authenticate button on your website.

When authentication is triggered, the website must send an authentication initialization request to the OneSpan Trusted Identity platform API:

The exact structure of the request body can be verified on the Platform API Sandbox page. The authInitializationResponse should look like this:

{
  "authenticationRequest": "{\"challenge\":\"acL8XO3TGqxZM4ZkSk0OZ_FTHsuv50H7Ebik8aX7e7a4RecqoAC_rifHO3BpThUTFtqw8iZZWhXmLhAravNT5g\",\"timeout\":1002,\"rpId\":\"test.onespan.com\",\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"obF6k-RPAE3GnN6iChWW7O28eEhkKDm5QAFivlY6b5Y\"}],\"userVerification\":\"preferred\",\"extensions\":{\"appId\":\"test\",\"txAuthSimple\":\"test string\",\"txAuthGeneric\":{\"contentType\":\"image/png\",\"content\":\"YWE\"},\"location\":true,\"uvi\":false,\"uvm\":false}}",
  "requestID": "authentication:d76814dc-e3ba-429b-96de-7087df26a228"
}

Communication with the client platform

During the authentication operation, the user must prove that they own the private key they used in the registration step. Therefore, the user must provide an assertion, which is generated by calling navigator.credentials.get() on the client-side JavaScript of your website. This retrieves the credentials that were generated in the registration operation, along with a signature.

navigator.credentials.get(publicKeyCredentialRequestOptions);

The publicKeyCredentialRequestOptions object should be based on the response received from the initialization step.

For more information on the structure, refer to the World Wide Web Consortium (W3C) Web Authentication documentation.

var resp = JSON.parse(authInitializationResponse.authenticationRequest);

var publicKeyCredentialRequestOptions = {
  publicKey: {
    rp: resp.rp,
    challenge: bufferDecode(resp.challenge),
    allowCredentials: decodeAllowCredentials(resp.allowCredentials),
    timeout: resp.timeout,
    userVerification: resp.userVerification,
    extensions: resp.extensions
  }
};

The bufferDecode function is defined in the following way:

function bufferDecode(value) {
  const s = base64DecodeUrl(value);
  const s1 = atob(s);
  return Uint8Array.from(s1, c => c.charCodeAt(0));
}

function base64DecodeUrl(str) {
  str = str + Array((4 - str.length % 4) % 4 + 1).join('=');
  return str.replace(/-/g, '+').replace(/_/g, '/');
}

The decodeAllowCredentials function is defined in the following way:

function decodeAllowCredentials(value) {
  if(value!== undefined){
    return value.map(ac => {
      return {
        type: ac.type,
        id: bufferDecode(ac.id)
      }
    })
  }
  return {};
}

The navigator.credentials.get() returns a promise with the PublicKeyCredential object.

For more information about the PublicKeyCredential, refer to the World Wide Web Consortium (W3C) Web Authentication documentation.

navigator.credentials.get(credentialAuthenticatorAssertionData)
  then(publicKeyCredential => {
    finalize(publicKeyCredential);
})
  catch(e => {
    // error handling
});

Finalization

In the finalization step, you need to send a request to the finalize endpoint of the OneSpan Trusted Identity platform APIClosed Provides the endpoints that are required for the successful completion of the operations.. The body is prepared based on the previously received PublicKeyCredential object.

The following POST request is sent to the OneSpan Trusted Identity platform API:

with the following body:

const authenticationResponse = {
  id: publicKeyCredential.id,
  rawId: toBase64Url(publicKeyCredential.rawId),
  type: "public-key",
    "authenticatorData": toBase64Url(publicKeyCredential.response.authenticatorData),
    "signature": toBase64Url(publicKeyCredential.response.signature),
    "userHandle": toBase64Url(publicKeyCredential.response.userHandle),
    "clientDataJSON": toBase64Url(publicKeyCredential.response.clientDataJSON)(publicKeyCredential.response.attestationObject),
  }
};

const body = {
  credentials: {
    fidoAuthenticator: {
      fidoProtocol: null,
      requestID: null,
      authenticationResponse: JSON.stringify(authenticationResponse)
    }
  },
  requestID: "c7922f36-5419-4658-84fb-8c9a8ebda168"
};

function toBase64Url(arrayBuffer) {
  let binary = '';
  const bytes = new Uint8Array(arrayBuffer);
  const len = arrayBuffer.byteLength;
  for (let i = 0; i < len; i++) {
  binary += String.fromCharCode(bytes[i]);
}
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}

The value of the requestID in the body should match the requestID that was retrieved from the authInitializationResponse.