Adding WebAuthn to the web application
Tags: javascript, react, webauthn, web
Published: 2023-12-02 11:02:12

Hello! In this article, we will learn how to add WebAuthn to web applications from the perspective of a frontend developer. WebAuthn represents a new authentication method that provides a higher level of security by replacing outdated passwords and SMS confirmations with public key-based authentication. This not only increases protection against unauthorized access, but also simplifies login for users.

{% embed https://www.youtube.com/watch?v=xzcY2qpQhEs&ab_channel=u4aew %}

### Basics of Public Key Encryption
To understand WebAuthn, it is important to be familiar with the concept of public key encryption. This is a method where two keys are used: a public key and a private key. The public key can be freely distributed and is used to encrypt messages. The ciphertext thus generated can only be decrypted with the corresponding private key, which should remain secret. This technology underlies WebAuthn, where public keys provide reliable authentication without the transmission of confidential data.

### How WebAuthn works

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vs2g5rssekbqi3pd2vqp.png)

WebAuthn operates on several key stages:

1. Creating a Challenge: The server generates a unique challenge and sends it to the user's device.

2. User Confirmation: The user confirms their intention to authenticate using biometrics, a PIN code, or another method. 

3. Signing the Challenge: The device signs the challenge with its private key, confirming the legitimacy of the request. 

4. Sending the Assertion: The device sends the signed challenge back to the site.

5. Assertion Verification: The site verifies the signature using a previously stored public key.Successful verification confirms the user's authentication.

### Example Implementation in a Web Application

In this part of the article, we will develop a simple web application using React.js. Our application will include a single field for entering a username and two functional buttons: 'Register' and 'Login'.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynsowzbr7u32zytlkvww.png)

**Registration**
To register a new user, we need to implement a function that requests the creation of new credentials through navigator.credentials.create(). An important component here is publicKeyCredentialCreationOptions, which defines the parameters for creating a new key. For a more detailed understanding of these parameters, I recommend consulting the [WebAuthn](https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialcreationoptions) documentation.

```javascript
// Function to create new credentials using the Web Authentication API.
const getCredential = async ({
    challenge, // Hash (challenge) received from the server
  }) => {
    // Options for creating a public key.
    const publicKeyCredentialCreationOptions = {
      rp: {
        name: 'my super app', // name of the Relying Party
        id: 'webauthn.fancy-app.site', // identifier of the Relying Party (Domain)
      },
      user: {
        id: Uint8Array.from(userId, (c) => c.charCodeAt(0)), // converting userId into Uint8Array
        name: 'User', // username 
        displayName: 'Full username', // user's display name
      },
      challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)), // converting challenge into Uint8Array
      pubKeyCredParams: [
        // preferred parameters for the cryptographic algorithm
        {
          type: 'public-key', // type of key
          alg: -7, // algorithm ECDSA with curve P-256
        },
        {
          type: 'public-key',
          alg: -257, // algorithm RSA with SHA-256 limitation
        },
      ],
      timeout: 60000, // response timeout (in milliseconds)
      excludeCredentials: [], // list of credentials to be excluded
      authenticatorSelection: { // criteria for selecting an authenticator
        residentKey: 'preferred',
        requireResidentKey: false,
        userVerification: 'required', // requirement for user verification
      },
      attestation: 'none', // type of attestation, not required here
      extensions: { // extensions
        credProps: true, // if true, credential properties are returned
      },
    };
    // API call to create new credentials using the provided options.
    return await navigator.credentials.create({
      publicKey: publicKeyCredentialCreationOptions,
    });
  };
```

This function returns a `PublicKeyCredential` object, which in JavaScript represents public credentials. They are created during registration through the WebAuthn API.

After these data are sent to the server, it conducts a verification of the public key and, upon successful verification, registers the user who sent it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89dpku8carne80tl17ix.png)

**Authentication**

For user authentication, we need to create a function that uses **`navigator.credentials.get()`**. This function will use **`publicKeyCredentialRequestOptions`** parameters, which should include **`challenge`** (the challenge) and **`rpId`** (the identifier of the relying party, usually the domain).

```javascript
const getAssertion = async ({ challenge }) => {
    // Creating an object with public key credential request options
    const publicKeyCredentialRequestOptions = {
      // Converting the challenge (provided by the server) to Uint8Array. 
      // This is necessary as WebAuthn requires the challenge to be in binary format.
      challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)),

      // An empty array indicating that any registered credentials can be used.
      // Specific credentials can be specified if there is a need to restrict access.
      allowCredentials: [],

      // The Relying Party ID (usually the domain) associated with the request.
      rpId: 'webauthn.fancy-app.site',

      // The timeout in milliseconds to complete the authentication process.
      timeout: 60000,

      // The level of user verification. In this case, confirmation ('required') is needed.
      // Other options may include 'preferred' or 'discouraged'.
      userVerification: 'required',
    };

    // Calling the get() method of navigator.credentials with the provided parameters.
    // This call initiates the process of obtaining an authentication assertion from the user.
    return await navigator.credentials.get({
      publicKey: publicKeyCredentialRequestOptions,
    });
};

```

The function will return a `PublicKeyCredential` object, which then needs to be sent to the server. This is necessary for the authentication procedure and subsequent acquisition of an access token.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtpucl6mz7yg12uibw13.png)

### Demo

If you would like to try out this functionality on your own device, please use the link to the [test stand](https://webauthn.fancy-app.site/). In the console, you will see the **`PublicKeyCredential`**, which is used for both registration and authentication. Also, for a detailed study of the code, I suggest the link to the [GitHub repository](https://github.com/u4aew/react-webauthn).

Thank you for your attention)