Product Documentation
  1. Copy the PREFIDO web application folder (prefido2) and rename the copy to postfido2.

    cp -r prefido2 postfido2
  2. Delete old users from the database.

    sqlite3 postfido2/db/aftdb.db
    delete from users;
    .exit
  3. Open postfido2/templates/register.html in preferred text editor.

    1. Copy this snippet of code (between the AAAAA... lines, but NOT including the AAAAA…. lines) and paste it between the AAAAA lines in register.html, replacing the existing content in the HTML file. This will remove the action and method attributes of the form to discard the old registration post request. This will be replaced by a call to a function in functions.js.

      <!-- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -->
      <form  id="regform" >
      <!-- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -->
    2. Copy this snippet of code (between the BBBBB... lines, but NOT including the BBBBB…. lines) and paste between the BBBBB lines in register.html, replacing the existing content in the HTML file. This will remove the passcontainer and password input and replacing it with id displayname input, which is used to identify the name of the FIDO2 Token used in registration.

      <!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
      <input class="input-out" type="text" id="displayname" name="displayname" placeholder="Display Name"><br>
      <!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
    3. Copy this snippet of code (between the CCCCC... lines) and paste between the CCCCC lines in register.html, replacing the existing content in the HTML file.
      This will remove the type, form, and value from the button and add onclick="submitForm('registration')". This will call the function that will be added to functions.js.

      <!-- CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC -->
      <button id="regbutton" onclick="submitForm('registration')">Sign Up</button>
      <!-- CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC -->
    4. Copy this snippet of code (between the DDDDD... lines) and paste between the DDDDD lines in register.html.

      <!-- DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD -->
      <script type="text/javascript" src="/js/common.js"></script>
      <!-- DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD -->
  4. Open postfido2/templates/login.html in your preferred text editor.

    1. Copy this snippet of code (between the EEEEE... lines) and paste between the EEEEE lines in login.html, replacing the existing content in the HTML file. This will remove the action and method aspects of the form to remove the old login post request. This will be replaced by a call to a function in functions.js.

      <!-- EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE -->
      <form id="loginform">
      <!-- EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE -->
    2. Delete the snippet of code (between the FFFFF... lines). This will delete the passcontainer and password input. They are no longer needed, thanks to FIDO2!

      <!-- FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -->
      <!-- FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -->
    3. Copy this snippet of code (between the GGGGG... lines) and paste between the GGGGG lines in login.html, replacing the existing content in the HTML file.
      This will remove the type, form, and value attributes from the button and add onclick="submitForm('authentication')". This will call the function that will be added to to functions.js.

      <!-- GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG -->
      <button onclick="submitForm('authentication')">Sign In</button>
      <!-- GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG -->
    4. Copy this snippet of code (between the HHHHH... lines) and paste between the HHHHH lines in register.html.

      <!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->
      <script type="text/javascript" src="/js/common.js"></script>
      <!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->
  5. Open postfido2/templates/js/functions.js in a preferred text editor.

    1. Copy this snippet of code (between the IIII... lines) and paste between the IIII lines in functions.js.
      This will add functions to the APPCLIENT that are used to request challenges from the APPSERVER, pass the challenges to the FIDO2 Token, and submit challenge results to the APPSERVER.

      //IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
      function submitForm(intent){
        if(intent=="registration"){
        $.post('/getChallenge', {
          'intent' : intent,
          'username': $('#regusername').val(),
          'displayname': $('#displayname').val(),
          'firstname': $('#firstname').val(),
          'lastname': $('#lastname').val()
        }).done(resp => {
          if(resp.Response == "sqlite-error"){
            console.log(resp.Response);
             location.reload();
          } else if(resp.Response == "skfs-error"){
             console.log(resp.Response);
          } else {
            document.getElementById("failed").style.display = "none";
            document.getElementById("failedbreak").style.display = "none";
            callFIDO2Token(intent,resp.Response);
          }
      
          }).fail((jqXHR, textStatus, errorThrown) => {
            alert(jqXHR, textStatus, errorThrown);
          });
        } else if(intent=="authentication")
        $.post('/getChallenge', {
          'intent' : intent,
          'username': $('#username').val()
        })
          .done((resp) => {
          if(!resp.Response.toString().toLowerCase().includes("error")){
            callFIDO2Token(intent,resp.Response);
          } else {
            alert("Username not registered");
          }
        })
        .fail((jqXHR, textStatus, errorThrown) => {
          alert(jqXHR, textStatus, errorThrown);
        });
      }
      
      function callFIDO2Token(intent,challenge) {
        let challengeBuffer = challengeToBuffer(challenge);
        let credentialsContainer = window.navigator;
        if(intent=="registration"){
          credentialsContainer.credentials.create({ publicKey: challengeBuffer.Response })
          .then(credResp => {
            let credResponse = responseToBase64(credResp);
            credResponse.intent = intent;
            $.post('/submitChallengeResponse',  credResponse)
            .done(regResponse => onResult(intent,regResponse))
            .fail((jqXHR, textStatus, errorThrown) => {
              console.log(jqXHR, textStatus, errorThrown);
            });
          })
          .catch(error => {
            alert(error);
          });
        } else if (intent=="authentication"){
          credentialsContainer.credentials.get({ publicKey: challengeBuffer.Response })
            .then(credResp => {
              let credResponse = responseToBase64(credResp);
              credResponse.intent = intent;
              $.post('/submitChallengeResponse', credResponse)
              .done(authResponse => onResult(intent,authResponse))
              .fail((jqXHR, textStatus, errorThrown) => {
                alert(jqXHR, textStatus, errorThrown);
              });
            })
            .catch(error => {
              alert(error);
            });
          }
        }
        function onResult(intent,response){
          if(intent=="registration"){
            if(!response.Response.toString().toLowerCase().includes("error")){
              window.location.replace(window.location.protocol + "//" + window.location.host + "/login");
            } else {
              alert(response.Response);
            }
        } else if(intent=="authentication"){
          if(response.Response.toString().includes("Successfully processed sign response")){
            window.location.replace(window.location.protocol + "//" + window.location.host + "/dashboard");
          } else {
            alert(response.Response);
          }
        }
      }
      
      //IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
  6. Create the file postfido2/templates/js/common.js.

    1. Copy this snippet of code (between the JJJJ... lines) and paste between the JJJJ lines in postfido2/templates/js/common.js. This code is used for encoding and decoding data to and from the FIDO2 Token.

      //JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
      function challengeToBuffer(input) {
      input = JSON.parse(input);
      input.Response.challenge = decode(input.Response.challenge);
      if(typeof input.Response.user !== 'undefined') {
      input.Response.user.id = decode(input.Response.user.id);
      }

      if (input.Response.excludeCredentials) {
      for (let i = 0; i < input.Response.excludeCredentials.length; i++) {
      input.Response.excludeCredentials[i].id = input.Response.excludeCredentials[i].id.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
      input.Response.excludeCredentials[i].id = decode(input.Response.excludeCredentials[i].id);
      }
      }

      if (input.Response.allowCredentials) {
      for (let i = 0; i < input.Response.allowCredentials.length; i++) {
      input.Response.allowCredentials[i].id = input.Response.allowCredentials[i].id.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
      input.Response.allowCredentials[i].id = decode(input.Response.allowCredentials[i].id);
      }
      }
      return input;
      }

      function responseToBase64(input) {
      let copyOfDataResponse = {};
      copyOfDataResponse.id = input.id;
      copyOfDataResponse.rawId = encode(input.rawId);
      if(typeof input.response.attestationObject !== 'undefined') {
      copyOfDataResponse.attestationObject = encode(input.response.attestationObject);
      }
      if(typeof input.response.authenticatorData !== 'undefined') {
      copyOfDataResponse.authenticatorData = encode(input.response.authenticatorData);
      copyOfDataResponse.signature = encode(input.response.signature);
      copyOfDataResponse.userHandle = encode(input.response.userHandle);
      }
      copyOfDataResponse.clientDataJSON = encode(input.response.clientDataJSON);
      copyOfDataResponse.type = input.type;
      return copyOfDataResponse;
      }

      let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
      let lookup = new Uint8Array(256);
      for (let i = 0; i < chars.length; i++) {
      lookup[chars.charCodeAt(i)] = i;
      }

      let encode = function (arraybuffer) {
      let bytes = new Uint8Array(arraybuffer),
      i, len = bytes.length, base64url = '';
      for (i = 0; i < len; i += 3) {
      base64url += chars[bytes[i] >> 2];
      base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
      base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
      base64url += chars[bytes[i + 2] & 63];
      }

      if ((len % 3) === 2) {
      base64url = base64url.substring(0, base64url.length - 1);
      } else if (len % 3 === 1) {
      base64url = base64url.substring(0, base64url.length - 2);
      }
      return base64url;
      };

      let decode = function (base64string) {
      let bufferLength = base64string.length * 0.75,
      len = base64string.length, i, p = 0,
      encoded1, encoded2, encoded3, encoded4;
      let bytes = new Uint8Array(bufferLength);
      for (i = 0; i < len; i += 4) {
      encoded1 = lookup[base64string.charCodeAt(i)];
      encoded2 = lookup[base64string.charCodeAt(i + 1)];
      encoded3 = lookup[base64string.charCodeAt(i + 2)];
      encoded4 = lookup[base64string.charCodeAt(i + 3)];
      bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
      bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
      bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
      }
      return bytes.buffer
      };

      //JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
  7. Create the file postfido2/constants.js.

    1. Copy this snippet of code (between the KKKK... lines) and paste between the KKKK lines in postfido2/constants.js. The current values of SKFS_HOSTNAME, SVCUSERNAME, and SVCPASSWORD default to pointing to a FIDO2SERVER hosted by StrongKey. Using SKFS, replace the example values for SKFS with the hostname of the FIDO2SERVER and the svcusername and svcpassword with the username and password used to access the FIDO2SERVER.

       //KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
       const DID = 1;
       const PROTOCOL = "FIDO2_0";
       const AUTHTYPE = "PASSWORD";
       const SVCUSERNAME = "svcfidouser";
       const SVCPASSWORD = "Abcd1234!";
      
       exports.SKFS_HOSTNAME = "demo4.strongkey.com";
       exports.SKFS_PORT="8181";
       exports.SVCINFO = {
         did: DID,
        protocol: PROTOCOL,
        authtype: AUTHTYPE,
        svcusername: SVCUSERNAME,
        svcpassword: SVCPASSWORD
       };
       
       exports.SKFS_PREAUTHENTICATE_PATH = '/skfs/rest/preauthenticate'
       exports.SKFS_AUTHENTICATE_PATH = '/skfs/rest/authenticate'
       exports.SKFS_PREREGISTRATION_PATH = '/skfs/rest/preregister'
       exports.SKFS_REGISTRATION_PATH = '/skfs/rest/register'
       exports.SKFS_GET_KEYS_INFO_PATH = '/skfs/rest/getkeysinfo'
       exports.SKFS_DEREGISTER_PATH = '/skfs/rest/deregister'
      
       exports.METADATA_VERSION = "1.0"
       exports.METADATA_LOCATION = "Cupertino, CA"
       //KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
  8. Open postfido2/routes.js in a preferred text editor.

    1. Copy this snippet of code (between the LLLL... lines) and paste between the LLLL lines in routes.js. This will add the HTTPS module and the constants file that will be used to call the FIDO2SERVER.

       //LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
       const https = require('https');
       const CONSTANTS = require('./constants');
      
       //LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
    2. Copy this snippet of code (between the MMMM... lines) and paste between the MMMM lines in routes.js, replacing the existing content in the JavaScript file. Replace the /loginSubmit post listener /registerSubmit with /getChallenge and /submitChallengeResponse listeners. The /getChallenge listener is used to request challenges from the FIDO2SERVER to send to the APPCLIENT for registration and authentication. The /submitChallengeResponse listener is used to send the responses received from the FIDO2 Token, sent by the APPCLIENT, to the FIDO2SERVER.

      // MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
      router.post("/getChallenge", (req,res) =>{
        var intent = req.body.intent;
        var username = req.session.username = req.body.username;
        if(intent=="authentication"){
          if(username == ""){
            res.redirect("/login");
            return;
          }
          var db = getDB();
          db.get(`select * from users where username = ? `,[username],
          (err, row) => {
            if (err) {
              log("ERROR: "+ err.message);
            }
            if (row) {
              req.session.possibleuserid = row.id;
              process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
              const data =JSON.stringify({
                svcinfo: CONSTANTS.SVCINFO,
                payload: {
                  username: username,
                  options: {}
                }
              });
              const options = {
                hostname: CONSTANTS.SKFS_HOSTNAME,
                port: CONSTANTS.SKFS_PORT,
                path: CONSTANTS.SKFS_PREAUTHENTICATE_PATH,
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json'
                }
              };
              const fido2Req = https.request(options, fido2Res => {
                log(`statusCode: ${fido2Res.statusCode}`);
                fido2Res.on('data', d => {
                  log("challengeBuffer=");
                  log(d);
                  res.json({Response:d.toString()});
                })
              });
              fido2Req.on('error', error => {
                log(error);
                res.json({Response:"skfs-error"});
              });
              fido2Req.write(data);
              fido2Req.end();
            } else {
              res.json({Response:"sqlite-error"});
            }
          });
        } else if(intent=="registration"){
          var firstname = req.session.firstname = req.body.firstname;
         var lastname = req.session.lastname = req.body.lastname;
          var displayname = req.session.displayname= req.body.displayname;
          if(username == "" | displayname=="" | firstname == "" | lastname == ""){
            res.redirect("/register");
            return;
          }
          var db = getDB();
          db.get(`select * from users where username = ? `,[username],
          (err, row) => {
            if (err) {
              log("ERROR: "+ err.message);
            }
            if (!row) {
              process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
              const data = JSON.stringify({
                svcinfo: CONSTANTS.SVCINFO,
                payload: {
                  username: username,
                  displayname: displayname,
                  options: {"attestation":"direct"},
                  extensions: "{}"
                }
              });
              const options = {
                hostname: CONSTANTS.SKFS_HOSTNAME,
                port: CONSTANTS.SKFS_PORT,
                path: CONSTANTS.SKFS_PREREGISTRATION_PATH,
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json'
                }
              };
              const fido2Req = https.request(options, fido2Res => {
                log(`statusCode: ${fido2Res.statusCode}`);
                fido2Res.on('data', d => {
                  log("challengeBuffer=");
                  log(d);
                  res.json({Response:d.toString()});
                })
              });
              fido2Req.on('error', error => {
                log(error);
                res.json({Response:"skfs-error"});
              });
              fido2Req.write(data);
              fido2Req.end();
            } else {
              failedRegistration=true;
              res.json({Response:"sqlite-error"});
            }
          });
        }
      });
      
      router.post("/submitChallengeResponse", (req,res) =>{
        var intent = req.body.intent;
        var username = req.session.username;
        var credResponse = req.body;
        var reqOrigin = req.get('host');
        let data = "";
        let path = "";
        if(intent=="authentication"){
          var metadataJSON = {
            version: CONSTANTS.METADATA_VERSION,
            last_used_location: CONSTANTS.METADATA_LOCATION,
            username: username,
            origin: "https://"+reqOrigin,
            clientUserAgent: req.headers['user-agent']
          };
          var responseJSON = {
            id: credResponse.id,
            rawId: credResponse.rawId,
            response: {
              authenticatorData: credResponse.authenticatorData,
              signature: credResponse.signature,
              userHandle: credResponse.userHandle,
              clientDataJSON: credResponse.clientDataJSON
            },
            type: "public-key"
          };
          data = JSON.stringify({
            svcinfo: CONSTANTS.SVCINFO,
            payload: {
              strongkeyMetadata: metadataJSON,
              publicKeyCredential: responseJSON,
            }
          });
          path = CONSTANTS.SKFS_AUTHENTICATE_PATH;
        } else if(intent=="registration"){
          var firstname = req.session.firstname;
          var lastname = req.session.lastname;
          var db = getDB();
          var metadataJSON = {
            version: CONSTANTS.METADATA_VERSION,
            create_location: CONSTANTS.METADATA_LOCATION,
            username: username,
            origin: "https://"+reqOrigin
          };
          var responseJSON = {
            id: credResponse.id,
            rawId: credResponse.rawId,
            response: {
              attestationObject: credResponse.attestationObject,
              clientDataJSON: credResponse.clientDataJSON
            },
            type: "public-key"
          };
          data = JSON.stringify({
            svcinfo: CONSTANTS.SVCINFO,
            payload: {
              strongkeyMetadata: metadataJSON,
              publicKeyCredential: responseJSON,
            }
          });
          path = CONSTANTS.SKFS_REGISTRATION_PATH;
        }
        const options = {
          hostname: CONSTANTS.SKFS_HOSTNAME,
          port: CONSTANTS.SKFS_PORT,
          path: path,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          }
        };
        const fido2Req = https.request(options, fido2Res => {
          log(`statusCode: ${fido2Res.statusCode}`);
          fido2Res.on('data', d => {
            if(d.toString().toLowerCase().includes("error")){
              res.json({Response:d.toString()});
              return;
            }
            if(intent == "registration"){
              db.run('insert into users(username,first_name,last_name) values(?,?,?)',[username,firstname,lastname], function(err) {
                if (err) {log("ERROR: "+ err.message);}
                log("user added: \nfirst name: "+firstname+"\nlast name: "+lastname+"\nusername: "+username);
                req.session.justReg=true;
                log(d);
                res.json({Response:d.toString()});
              });
            } else if(intent == "authentication"){
              req.session.userid = req.session.possibleuserid;
              log(d);
              res.json({Response:d.toString()});
            }
          })
        });
        fido2Req.on('error', error => {
          log(error);
          res.json({Response:"error"});
        });
        process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
        fido2Req.write(data);
        fido2Req.end();
      });
      // MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
    3. Copy this snippet of code (between the NNNN... lines) and paste between the NNNN lines in routes.js, replacing the existing content in the JavaScript file.
      Modify the deleteUser listener by replacing the code below with two post requests to FIDO2SERVER. The first post request is to /skfs/rest/getkeysinfo, which is used to retrieve the user's FIDO2 Token's keyid. The second request is to /skfs/rest/deregister, which deletes the FIDO2 Token registration information. This deletes the user’s FIDO2 Token from the FIDO2SERVER’s database at the same time the user’s info is deleted from the APPSERVER database.

       //NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
         var username = req.session.username;
         req.session.userid=null;
         log("logout user " +id +" "+username);
      
          db.run(`delete from users where id = ?`, id, function(err) {
              if (err) {
       	 return console.error(err.message);
              }
              process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
         const data = JSON.stringify({
           svcinfo: CONSTANTS.SVCINFO,
           payload: {
       	username: username
           }
         });
         const options = {
           hostname: CONSTANTS.SKFS_HOSTNAME,
           port: CONSTANTS.SKFS_PORT,
           path: CONSTANTS.SKFS_GET_KEYS_INFO_PATH,
           method: 'POST',
           headers: {
             'Content-Type': 'application/json',
             'Content-Length': data.length
           }
         };
         const fido2Req = https.request(options, fido2Res => {
           log(`statusCode: ${fido2Res.statusCode}`);
      
           fido2Res.on('data', d => {
             log("keyInfo=");
             log(d);
             const dataDel = JSON.stringify({
       	svcinfo: CONSTANTS.SVCINFO,
       	payload: {
       	    "keyid": JSON.parse(d).Response.keys[0].keyid
       	}
             });
             const optionsDel = {
       	hostname: CONSTANTS.SKFS_HOSTNAME,
       	port: CONSTANTS.SKFS_PORT,
       	path: CONSTANTS.SKFS_DEREGISTER_PATH,
       	method: 'POST',
       	headers: {
       	  'Content-Type': 'application/json',
       	  'Content-Length': dataDel.length
       	}
             };
             const fido2ReqDel = https.request(optionsDel, fido2ResDel => {
       	log(`statusCode: ${fido2ResDel.statusCode}`);
      
       	fido2ResDel.on('data', dDel => {
       	  log(dDel);
       	  log("deleted user " +id);
       	  req.session.justUserDeleted = true;
       	  res.redirect("/login");
       	})
             });
             fido2ReqDel.on('error', errorDel => {
       	log(errorDel);
       	res.json({Response:"skfs-error"});
             });
             fido2ReqDel.write(dataDel);
             fido2ReqDel.end();
           })
         });
         fido2Req.on('error', error => {
           log(error);
           res.json({Response:"skfs-error"});
         });
         fido2Req.write(data);
         fido2Req.end();
          });
       //NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
  9. Deploy the POSTFIDO web application. Open a terminal and navigate to the /postfido2 directory. Run the following commands:

    • CentOS/Ubuntu.

      sudo pm2 delete main
      sudo pm2 start main.js
      [OPTIONAL] sudo pm2 startup systemd
      sudo pm2 save
      tail -f log
    • Windows/Mac.

      sudo pm2 delete main
      sudo pm2 start main.js
      sudo pm2 save

    Run this command if the website fails to start:

    sudo node main.js
  10. Browse to https://fido2tutorial.strongkey.com:3001.