Setting Up endpoints for WebAuthn

Now, the largest part: implementing WebAuthn in our server

Registration endpoints

app.post("/auth/webauth-registration-options", (req, res) =>{
    const user = findUser(req.body.email);

    const options = {
        rpName: 'Coffee Masters',
        rpID,
        userID: user.email,
        userName: user.name,
        timeout: 60000,
        attestationType: 'none',
        
        /**
         * Passing in a user's list of already-registered authenticator IDs here prevents users from
         * registering the same device multiple times. The authenticator will simply throw an error in
         * the browser if it's asked to perform registration when one of these ID's already resides
         * on it.
         */
        excludeCredentials: user.devices ? user.devices.map(dev => ({
            id: dev.credentialID,
            type: 'public-key',
            transports: dev.transports,
        })) : [],

        authenticatorSelection: {
            userVerification: 'required', 
            residentKey: 'required',
        },
        /**
         * The two most common algorithms: ES256, and RS256
         */
        supportedAlgorithmIDs: [-7, -257],
    };

    /**
     * The server needs to temporarily remember this value for verification, so don't lose it until
     * after you verify an authenticator response.
     */
    const regOptions = SimpleWebAuthnServer.generateRegistrationOptions(options)
    user.currentChallenge = regOptions.challenge;
    db.write();
    
    res.send(regOptions);
});

app.post("/auth/webauth-registration-verification", async (req, res) => {
    const user = findUser(req.body.user.email);
    const data = req.body.data;
  
    const expectedChallenge = user.currentChallenge;
  
    let verification;
    try {
      const options = {
        credential: data,
        expectedChallenge: `${expectedChallenge}`,
        expectedOrigin,
        expectedRPID: rpID,
        requireUserVerification: true,
      };
      verification = await SimpleWebAuthnServer.verifyRegistrationResponse(options);
    } catch (error) {
    console.log(error);
      return res.status(400).send({ error: error.toString() });
    }
  
    const { verified, registrationInfo } = verification;
  
    if (verified && registrationInfo) {
      const { credentialPublicKey, credentialID, counter } = registrationInfo;
  
      const existingDevice = user.devices ? user.devices.find(
        device => new Buffer(device.credentialID.data).equals(credentialID)
      ) : false;
  
      if (!existingDevice) {
        const newDevice = {
          credentialPublicKey,
          credentialID,
          counter,
          transports: data.response.transports,
        };
        if (user.devices==undefined) {
            user.devices = [];
        }
        user.webauthn = true;
        user.devices.push(newDevice);
        db.write();
      }
    }
  
    res.send({ ok: true });

});

Login/Authentication Endpoints



app.post("/auth/webauth-login-options", (req, res) =>{
    const user = findUser(req.body.email);
    // if (user==null) {
    //     res.sendStatus(404);
    //     return;
    // }
    const options = {
        timeout: 60000,
        allowCredentials: [],
        devices: user && user.devices ? user.devices.map(dev => ({
          id: dev.credentialID,
          type: 'public-key',
          transports: dev.transports,
        })) : [],
        userVerification: 'required',
        rpID,
    };
    const loginOpts = SimpleWebAuthnServer.generateAuthenticationOptions(options);
    if (user) user.currentChallenge = loginOpts.challenge;
    res.send(loginOpts);
});

app.post("/auth/webauth-login-verification", async (req, res) => {
    const data = req.body.data;
    const user = findUser(req.body.email);
    if (user==null) {
        res.sendStatus(400).send({ok: false});
        return;
    } 
  
    const expectedChallenge = user.currentChallenge;
  
    let dbAuthenticator;
    const bodyCredIDBuffer = base64url.toBuffer(data.rawId);

    for (const dev of user.devices) {
      const currentCredential = Buffer(dev.credentialID.data);
      if (bodyCredIDBuffer.equals(currentCredential)) {
        dbAuthenticator = dev;
        break;
      }
    }

    if (!dbAuthenticator) {
      return res.status(400).send({ ok: false, message: 'Authenticator is not registered with this site' });
    }
  
    let verification;
    try {
      const options  = {
        credential: data,
        expectedChallenge: `${expectedChallenge}`,
        expectedOrigin,
        expectedRPID: rpID,
        authenticator: {
            ...dbAuthenticator,
            credentialPublicKey: new Buffer(dbAuthenticator.credentialPublicKey.data) // Re-convert to Buffer from JSON
        },
        requireUserVerification: true,
      };
      verification = await SimpleWebAuthnServer.verifyAuthenticationResponse(options);
    } catch (error) {
      return res.status(400).send({ ok: false, message: error.toString() });
    }
  
    const { verified, authenticationInfo } = verification;
  
    if (verified) {
      dbAuthenticator.counter = authenticationInfo.newCounter;
    }
  
    res.send({ 
        ok: true, 
        user: {
            name: user.name, 
            email: user.email
        }
    });
});

Finally, we add these endpoints to API.js

webAuthn: {
    loginOptions: async (email) => {
        return await API.makePostRequest(API.endpoint + "webauth-login-options", { email });
    },
    loginVerification: async (email, data) => {
        return await API.makePostRequest(API.endpoint + "webauth-login-verification", {
            email,
            data
        });                       
    },
    registrationOptions: async () => {
        return await API.makePostRequest(API.endpoint + "webauth-registration-options", Auth.account);           
    },
    registrationVerification: async (data) => {
        return await API.makePostRequest(API.endpoint + "webauth-registration-verification", {
            user: Auth.account,
            data
        });                       
    }
},