Product Documentation

In a typical new password-based registration, a web application will provide a form asking for a unique identifier, usually a username and a password (twice). Upon receiving the username and password, the web application will verify that the username has not been already taken by another user. If it has not, the web application will typically pass the username and password to its authentication module that will salt and hash the password before storing the username and now-hashed password in a database.

In a new FIDO2 registration workflow, rather than storing a username and hashed password, the workflow seeks to store a username and cryptographic public key. However, FIDO2 introduces many new concepts that are best explained in detail. The steps to achieve this are listed in the following steps.

  1. User submits identification information for the credential.



    Figure 1—1: Registration: User supplies username and displayName to the web form.


    In a new FIDO2 registration, a web application will provide a form asking for a username and a displayName. The username serves the same function as it does in the password-based workflow. The displayName is unrelated to security and is a means to set a human-readable identifier to the key pair being created. It is strongly recommended that web application developers use the string “Initial Registration” for the displayName and do not allow the user to modify this; every unique website will require the user to go through an “initial FIDO2 registration” process when enabling their FIDO2 Authenticator for that site, and it is unlikely that most users will forget their first FIDO2 Authenticator and the initial registrations at websites. When enabling modifications to the displayName in the web application, programmers might want to consider allowing users to only append to the “Initial Registration” string so as to preserve the context for the user even as they add many other FIDO2 Authenticators to a website. The HTML form used in the basic server demo is shown below.

    <div id="regPanel" class="form-panel">
      <h2>Register</h2>
      <div id="regUsernamePanel">
        <input autocomplete="off" class="met" id="regUsername" placeholder="Username" required="" type="text">
      </div>
      <div>
        <input autocomplete="off" class="met" id="regDisplayName" placeholder="Initial Registration" required="" type="text">
      </div>
      <div>
        <button class="met" id="regSubmit">Register New Key</button>
      </div>
    </div>


  2. The JS front end sends username and displayName to the web application back end.



    Figure 1—2: Registration: Form fields are submitted to web application back end.


    Upon the user submitting the information to the web form, the service provider's web application JS front end validates the information and calls a web service—named 'preregister' in the JS code to align with the SKFS preregister web service—to handle the registration process.

    this.post('preregister', {
      'username': $('#regUsername').val(),
      'displayName': $('#regDisplayName').val()
    })


  3. The submitted username is checked for uniqueness and FIDO2-enablement.



    Figure 1—3: Registration: The web application ensures the username is unique; the user is NOT FIDO2-enabled.


    Upon receiving the username and displayName, the service provider's web application back end verifies that the username is not already in use and that the user has NOT previously registered a FIDO2 key with this site (see Figure 1–3). If either verification fails to pass the test, the back-end application must respond appropriately to the user. If the username is not already in use and if the user has not previously registered a FIDO2 key, the service provider's web application proceeds with the next step.
    String username = getValueFromInput(Constants.RP_JSON_KEY_USERNAME, input);
    String displayName = getValueFromInput(Constants.RP_JSON_KEY_DISPLAYNAME, input);
    
    //Verify User does not already exist
    if (!doesAccountExists(username)){
      String prereg = SKFSClient.preregister(username, displayName);
      HttpSession session = request.getSession(true);
      session.setAttribute(Constants.SESSION_USERNAME, username);
      session.setAttribute(Constants.SESSION_ISAUTHENTICATED, false);
      session.setMaxInactiveInterval(Constants.SESSION_TIMEOUT_VALUE);
      return generateResponse(Response.Status.OK, prereg);
    }


  4. The web application sends a preregister web service request to SKFS.



    Figure 1—4: Registration: The web application sends a preregister web service request to SKFS.


    Here is the JSON for the preregister input:

    {
      "svcinfo": {
        "did": 1,
        "protocol": "FIDO2_0",
        "authtype": "PASSWORD",
        "svcusername": "svcfidouser",
        "svcpassword": "Abcd1234!"
      },
      "payload": {
        "username": "johndoe",
        "displayName": "Initial Registration",
        "options": {
          "attestation": "direct"
        },
        "extensions": "{}"
      }
    }

    The service provider's web application now sends a preregister web service request to SKFS, using the REST protocol, passing the username and displayName to SKFS via a POST request (see Figure 1–4). The Java that generates this request body is shown below.

    public static String preregister(String username, String displayName) {
        JsonObjectBuilder payloadBuilder = Json.createObjectBuilder()
            .add(Constants.SKFS_JSON_KEY_USERNAME, username)
            .add(Constants.SKFS_JSON_KEY_DISPLAYNAME, displayName)
            .add(Constants.SKFS_JSON_KEY_OPTIONS, getRegOptions())
            .add("extensions", Constants.JSON_EMPTY);
        return callSKFSRestApi(
            APIURI + Constants.REST_SUFFIX + Constants.PREREGISTER_ENDPOINT,
            payloadBuilder);
    }
    ...
    private static String callSKFSRestApi(String requestURI, JsonObjectBuilder payload){ JsonObjectBuilder svcinfoBuilder = Json.createObjectBuilder() .add("did", SKFSDID) .add("protocol", PROTOCOL) .add("authtype", Constants.AUTHORIZATION_HMAC); JsonObject body = Json.createObjectBuilder() .add("svcinfo", svcinfoBuilder) .add("payload", payload).build(); ...

    The protocol is used to tell SKFS to generate a challenge for the FIDO2 protocol (instead of the U2F protocol, which SKFS also supports). The username and displayName are described above. Options are used to specify preferences for the Authenticator used (i.e., a platform vs. cross-platform Authenticator, preferences for Authenticator attestation, preference for user verification vs. user presence, etc.). Extensions are defined in the WebAuthn specification. Options and extensions will not be addressed in this guide.

    NOTE: StrongKey’s APIs are described in more detail in FIDO2 v3 API Mechanics.


  5. In response, SKFS returns a challenge to the web application.



    Figure 1—5: Registration: SKFS returns a challenge to the web application.


    Upon receiving the preregister request, SKFS returns a challenge to the service provider web application.

    {
        "Response": {
            "rp": {
                "name": "StrongKey POC",
                "id": "strongkey.com"
            },
            "user": {
                "name": "johndoe",
                "id": "CXW...FMK4",
                "displayName": "Initial Registration"
            },
            "challenge": "YGmdBIb0JGVE6ZXucUn_Ew",
            "pubKeyCredParams": [{
                "type": "public-key",
                "alg": -7
            }, ...
            {
                "type": "public-key",
                "alg": -39
            }],
            "excludeCredentials": [],
            "attestation": "direct"
        }
    }


  6. The challenge passes to the JS code in the web application.



    Figure 1—6: Registration: The challenge is passed to the JS front-end code in the web application.


    The FIDO2 Server response containing the challenge is base64url-encoded. This must be converted to the ArrayBuffer datatype before being sent to the Authenticator.

    // base64url-decode preregister response from SKFS
    let challengeBuffer = this.preregToBuffer(preregResponse);


  7. The browser code JS sends the challenge to the Authenticator.



    Figure 1—7: Registration: The JS front-end code sends the challenge to the FIDO2 Authenticator.


    Having made the JavaScript preregister request (Step 4) the JS code receives the challenge from the back-end application and delivers the challenge to the Authenticator. The JS code is shown here (with the later-used portion highlit in orange):

    // Convert base64url fields to ArrayBuffer format [verify].
    let challengeBuffer = this.preregToBuffer(preregResponse);
    // Browser passes challenge fields to WebAuthn API, which tells relevant FIDO2 authenticators to generate a new set of public key credentials
    let credentialsContainer = window.navigator;
    credentialsContainer.credentials.create({ publicKey: challengeBuffer.Response })
      .then(credResp => {
        // convert response to base64url
        let credResponse = this.preregResponseToBase64(credResp);
          this.post('register', credResponse)
          .done(regResponse => that.onRegResult(regResponse))
            .fail((jqXHR, textStatus, errorThrown) => {
            this.onFailError(jqXHR, textStatus, errorThrown);
            });
          });


  8. The Authenticator demands a test of human presence.


    Figure 1—8: Registration: The Authenticator demands a test of user presence.


    Upon receiving the challenge and performing internal sanity checks, the Authenticator attached to the user’s system will signal the user to confirm user presence by requiring a gesture which may vary by Authenticator—examples might include a button to be pressed, a request for a biometric scan, or a blinking LED. This gesture typically happens by touching the Authenticator.

  9. The Authenticator generates a key pair, then digitally signs the response back to the web application JS code.



    Figure 1—9: Registration: The Authenticator generates a key pair and sends a signed challenge to the web application JavaScript code.


    Having received confirmation of the test of user presence, the Authenticator generates a new key pair, creates additional metadata, and sends the response to the JS in the web application front end using one of five different attestation types defined in FIDO2.

  10. The JS code sends the response to the web application.



    Figure 1—10: Registration: The JS code sends the response to the web application.


    To complete the registration process, the JS application now must base64url-encode the response from the Authenticator before sending it to the back end of the service provider web application. Highlit portions represent the earlier-used section of code.

    // Convert base64url fields to ArrayBuffer format [verify].
    let challengeBuffer = this.preregToBuffer(preregResponse);
    // Browser passes challenge fields to WebAuthn API, which tells relevant FIDO2 authenticators to generate a new set of public key credentials
    let credentialsContainer = window.navigator;
    credentialsContainer.credentials.create({ publicKey: challengeBuffer.Response })
      .then(credResp => {
        // base64url-encode response from the Authenticator
        let credResponse = this.preregResponseToBase64(credResp);
          // Call the register web service on the web app back end
          this.post('register', credResponse)
          .done(regResponse => that.onRegResult(regResponse))
            .fail((jqXHR, textStatus, errorThrown) => {
            this.onFailError(jqXHR, textStatus, errorThrown);
            });
          });


  11. The web application calls the register web service on SKFS.



    Figure 1—11: Registration: The web application back end calls the register web service on SKFS.


    The SKFS register web service must receive this JSON data structure (which is not displayed in full for brevity’s sake); for the full response, see FIDO2 v3 API registration Request Body):

    {
      "svcinfo": {
        "did": 1,
        "protocol": "FIDO2_0",
        "authtype": "PASSWORD",
        "svcusername": "svcfidouser",
        "svcpassword": "Abcd1234!"
      },
      "payload": {
        "publicKeyCredential": {
          "id": "MBDVx...c8wA",
          "rawId": "MBDVx...c8wA",
          "response": {
              "attestationObject": "o2Nm...ZqFA",
              "clientDataJSON": "eyJ0...bSJ9"
          },
          "type": "public-key"
        },
        "strongkeyMetadata": {
          "version": "1.0",
          "create_location": "Sunnyvale, CA",
          "origin": "https://<FQDN>",
          "username": "johndoe"
        }
      }
    }

    The Authenticator response is sent to SKFS via a register web service request. The Java code that generates this request is shown below.

    public static String register(String username, String origin, JsonObject signedResponse) {
        JsonObject reg_metadata = javax.json.Json.createObjectBuilder()
            .add("version", PROTOCOL_VERSION) // ALWAYS since this is just the first revision of the code
            .add("create_location", "Sunnyvale, CA")
            .add("username", username)
            .add("origin", origin).build();
        JsonObjectBuilder reg_inner_response = javax.json.Json.createObjectBuilder()
            .add("attestationObject", signedResponse.getJsonObject("response").getString("attestationObject"))
            .add("clientDataJSON", signedResponse.getJsonObject("response").getString("clientDataJSON"));
        JsonObject reg_response = javax.json.Json.createObjectBuilder()
            .add("id", signedResponse.getString("id"))
            .add("rawId", signedResponse.getString("rawId"))
            .add("response", reg_inner_response) // inner response object
            .add("type", signedResponse.getString("type")).build();
        JsonObjectBuilder payloadBuilder = Json.createObjectBuilder()
            .add("publicKeyCredential", reg_response)
            .add("strongkeyMetadata", reg_metadata);
        return callSKFSRestApi(
            APIURI + Constants.REST_SUFFIX + Constants.REGISTER_ENDPOINT,
            payloadBuilder);
    }
    ...
    private static String callSKFSRestApi(String requestURI, JsonObjectBuilder payload){ JsonObjectBuilder svcinfoBuilder = Json.createObjectBuilder() .add("did", SKFSDID) .add("protocol", PROTOCOL) .add("authtype", Constants.AUTHORIZATION_HMAC); JsonObject body = Json.createObjectBuilder() .add("svcinfo", svcinfoBuilder) .add("payload", payload).build(); ...


  12. SKFS verifies response and optionally validates the Authenticator's attestation.




    Figure 1—12: Registration: SKFS validates the challenge response and (optionally) attestation.




  13. SKFS registers the user's public key by storing it.




    Figure 1—13: Registration: The user's public key is persisted into the database.


    On successful verification of the Authenticator’s response, StrongKey FIDO Server will store the user’s public key in its database.

  14. The success response is sent to the service provider web application.




    Figure 1—14: Registration: The success response is returned to the web application.


    Upon receiving the register request and processing the Authenticator’s response, SKFS returns an appropriate response to the service provider web application.

  15. FIDO2 registration is stored and enabled for this user.




    Figure 1—15: Registration: FIDO2 is enabled for the user in the web application database.


    Optionally, the service provider web application may store information about the successful registration in its own database and perform additional processing as needed.

  16. The success response is sent to the web application JS code.




    Figure 1—16: Registration: The success response is sent to the browser.


    The message received (typically an HTTP 200 OK) by the web application front end is in response to its own register web service.

  17. The user is notified that they are now registered with a FIDO2 key.




    Figure 1—17: Registration: Success! The user has completed initial registration.