📘

Supported Languages

We provide client web SDKs in the following languages: JavaScript/TypeScript. You don't need to use TypeScript to be able to use our SDK. TypeScript is just a layer of enhancements like types used during development, once you publish to production, the code will be running on end users browsers in JavaScript anyway.

Installation

Install the client SDK of your choice by running a command in your terminal, or by using a dependency management tool specific to your project.

# Run this command to install the package.
npm install @prove-identity/prove-auth

Determine the Type of Flow: Mobile or Desktop

You can determine if the user is on a mobile or desktop browser using this example. If the isMobile is true, you should pass mobile to the Start() function on the server, otherwise you can pass desktop:

// Check if the end user is on a mobile or desktop browser.
const authCheck = new proveAuth.AuthenticatorBuilder().build();
let isMobile = authCheck.isMobileWeb()
// Check if the end user is on a mobile or desktop browser.
const authCheck = new proveAuth.AuthenticatorBuilder().build();
let isMobile = authCheck.isMobileWeb()

In a mobile flow, Mobile Auth is attempted first and if that fails, it will perform OTP validation on the mobile phone. In a desktop flow, an Instant Link is sent via text message to the mobile phone for verification.

In the mobile flow, once either Mobile Auth or the OTP validation is complete, the AuthFinishStep function will be called.

In the desktop flow, a WebSocket will be opened for 2 minutes on the desktop browser while waiting for the user to click on the link in the text message. Once the link is clicked, the WebSocket will close and the AuthFinishStep function will be called.

Authenticate()

The SDK requires an authToken as a parameter for the Authenticate() function. This token is returned from the Start() call of the server SDK. The token is session specific so it can only be used for a single flow so it cannot be reused. It also expires after 15 minutes.

Retrieve authToken

To start the flow, you'll need to send a request to your backend server with the phone number, flow type, and an optional challenge of either the date of birth (format: YYYY-MM-DD) or social security number (format: last 4 digits).

async function initialize(phoneNumber, ssn, flowType) {
  const response = await fetch(backendUrl + "/initialize", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      phoneNumber: phoneNumber,
      flowType: flowType,
      ssn: ssn,
    }),
  });

  const rsp = await response.json();
  const authToken = rsp.authToken;
  
  return authToken;
}
async function initialize(
  phoneNumber: string,
  ssn: string,
  flowType: string
): Promise<string> {
  const response = await fetch(backendUrl + "/initialize", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      phoneNumber: phoneNumber,
      flowType: flowType,
      ssn: ssn,
    }),
  });

  const rsp = await response.json();
  const authToken = rsp.authToken;

  return authToken;
}

Setup Authenticator

Once you have the authToken, build the authenticator for both the mobile and desktop flows.

async function authenticate(isMobileWeb, authToken) {
  // Set up the authenticator for either mobile or desktop flow.
  let builder = new proveAuth.AuthenticatorBuilder();

  if (isMobileWeb) {
    // Set up Mobile Auth and OTP.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withMobileAuthImplementation("fetch")
      .withOtpFallback(otpStart, otpFinish);
  } else {
    // Set up Instant Link.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withInstantLinkFallback(instantLink)
      .withRole("secondary");
  }

  const authenticator = builder.build();

  // Authenticate with the authToken.
  return authenticator.authenticate(authToken);
} 
async function authenticate(isMobileWeb: boolean, authToken: string) {
  // Set up the authenticator for either mobile or desktop flow.
  let builder = new proveAuth.AuthenticatorBuilder();

  if (isMobileWeb) {
    // Set up Mobile Auth and OTP.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withMobileAuthImplementation("fetch")
      .withOtpFallback(otpStart, otpFinish);
  } else {
    // Set up Instant Link.
    builder = builder
      .withAuthFinishStep((input) => verify(input.authId))
      .withInstantLinkFallback(instantLink)
      .withRole("secondary");
  }

  const authenticator = builder.build();

  // Authenticate with the authToken.
  return authenticator.authenticate(authToken);
}

Validate the Mobile Phone

In the AuthFinishStep, you'll specify a function to call once the possession checks are complete on the mobile phone. This endpoint on your backend server will then call the Validate() function to check if the phone number was validated. If it was successful, the server should return the results from the Challenge() function that will include user information. If it was not successful, the server won't return user information. We've included a few example fields that should be returned and then pre-filled on a form for the user to verify.

// Send a verify request to get return user information.
async function verify() {
  const response = await fetch(backendUrl + "/verify", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  });

  const results = await response.json();
  const rsp = JSON.stringify(results);

  const firstName = document.getElementById("firstNameInput");
  const lastName = document.getElementById("lastNameInput");

  firstName.value = rsp.firstName;
  lastName.value = rsp.lastName;

  return null;
}
// Send a verify request to get return user information.
async function verify() {
  const response = await fetch(backendUrl + "/verify", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  });

  const results = await response.json();
  const rsp = JSON.stringify(results);

  const firstName = document.getElementById("firstNameInput");
  const lastName = document.getElementById("lastNameInput");

  firstName.value = rsp.firstName;
  lastName.value = rsp.lastName;

  return null;
}

OTP Configuration

There are two functions to implement for the OTP handling - a start and a finish step.

In order to set the OTP handler, withOtpFallback(startStep: OtpStartStep | OtpStartStepFn, finishStep: OtpFinishStep | OtpFinishStepFn), OtpStartStep and OtpFinishStep interfaces should be implemented. The JavaScript snippet has a simplified example while the TypeScript snippet explains various situations.

function otpStart(phoneNumberNeeded, phoneValidationError) {
  return new Promise((resolve, reject) => {
    if (phoneNumberNeeded) {
      var val = prompt("Enter phone number:");
      let input = {
        phoneNumber: val,
      };
      resolve(input);
    } else {
      resolve(null);
    }
  });
}
const otpStartStep: OtpStartStep = {
  execute: async (
    phoneNumberNeeded: boolean,
    phoneValidationError?: PhoneValidationError
  ): Promise<OtpStartInput | null> => {
    return new Promise((resolve, reject) => {
      if(phoneNumberNeeded) {
        // If phone number is required
        if (phoneValidationError) {
          // phoneValidationError, if provided and not `undefined`, indicates that the last submitted
          // phone number is invalid but users can still retry (allowed up to three tries).
          // Update your web's UI here if need to inform users about the failed attempt and the retry.
        }

        // Example UI callback to listen to users' submission of the phone number.
        const submitPhoneNumber = (phoneNumber) => {
          let input: OtpStartInput = {
            phoneNumber: phoneNumber ?? ''
          }
          resolve(input);
        }

        // Example UI callback to listen to users' cancelling the OtpStartStep / OTP fallback flow.
        const cancelStartStep = () => {
          reject("User canceled OTP flow.");
        }
      } else {
        // When phone number is not required since it was already delivered to the Prove Auth server
        // in the Auth Start request, just call resolve(null) and move forward with the finish step.
        resolve(null)
      }
    });
  };

Call the resolve(input: OtpStartInput) method to return the collected phone number to the SDK, where the phoneNumber value is wrapped with OtpStartInput.

If the phone number was already specified in the Start() call, then resolve(null) should be called to communicate to the SDK that the mobile app has the user's agreement to deliver the SMS OTP message to the provided phone number.

Call the reject("some error message") method to communicate to the SDK any issues while trying to obtain the phone number. An error should be reported if the user explicitly cancels the SMS OTP transaction or presses the back button to leave the SMS OTP start step screen.

function otpFinish(err) {
  return new Promise((resolve, reject) => {
    if (err) {
      console.log(err);
    } else {
      var val = prompt(`Enter your OTP:`);

      let result = {
        input: {
          otp: val,
        },
        resultType: 0,
      };

      resolve(result);
    }
  });
}
const otpFinishStep: OtpFinishStep = {
  execute: async (otpError?: OtpError): Promise<OtpFinishResult> => {
    return new Promise((resolve, reject) => {
      if(otpError) {
        // otpError, if provided and not `undefined`, indicates that the last submitted
        // OTP is invalid but users can still retry (allowed up to three tries).
        // Update your web UI if need to inform users about the failed attempt and the retry option.
      }

      // Example UI callback to listen to users' submission of the OTP codes:
      const submitOtp = (otp) => {
          let input: OtpFinishInput = {
              otp: otp ?? ''
          }
          // To submit OTP values for validation, create result object containing input and OnSuccess enum type.
          let result: OtpFinishResult = {
              input: input,
              resultType: OtpFinishResultType.OnSuccess
          }
          resolve(result);
      };

      // Example UI callback to listen to users' cancelling the OTP submission.
      const cancelOtp = () => {
        // User would like to cancel the OTP flow
        reject("User canceled OTP flow.")
      };

      // Example UI callback to listen to users' interaction to resend SMS OTP.
      const resendOtpCode = () => {
        // To trigger resend SMS OTP, create result object containing OnResendOtp enum.
        resolve({resultType: OtpFinishResultType.OnResendOtp})
      };
    });
  }
};

Call the resolve(result: OtpFinishResult) method to return the collected OTP value in which result variable should contain OnSuccess enum value for OtpFinishResultType and the OTP value wrapped in OtpFinishInput.

Call the reject("some error message") method to communicate to the SDK any issues while trying to obtain the OTP value. An error should be reported if the user explicitly cancels the SMS OTP transaction or presses the back button to exit out of the SMS OTP finish step screen.

Also call the resolve(result: OtpFinishResult) method to request a new SMS OTP message in which the result variable should contain OnResendOtp as enum value for OtpFinishResultType. The SDK will then initiate a new OtpStartStep.execute() call to allow the mobile app to restart the phone number collection logic. The SDK allows for up to 3 OTPs to be sent during the same authentication session.

Instant Link Configuration

There is one function to configuration for Instant Link.

In order to set the Instant Link handler, withInstantLinkFallback(startStep: InstantLinkStartStep | InstantLinkStartStepFn) requires implementing the InstantLinkStartStep interface. The JavaScript snippet has a simplified example while the TypeScript snippet explains various situations.

function instantLink(phoneNumberNeeded, phoneValidationError) {
  return new Promise((resolve, reject) => {
    if (phoneNumberNeeded) {
      var val = prompt("Enter phone number:");
      let input = {
        phoneNumber: val,
      };
      resolve(input);
    } else {
      resolve(null);
    }
  });
}
const instantLinkStartStep: InstantLinkStartStep = {
  execute: async (
    phoneNumberNeeded: boolean,
    phoneValidationError?: PhoneValidationError
  ): Promise<InstantLinkStartInput | null> => {
    return new Promise((resolve, reject) => {
      if(phoneNumberNeeded) {
        // If phone number is required
        if (phoneValidationError) {
          // phoneValidationError, if provided and not `undefined`, indicates that the last submitted
          // phone number is invalid but users can still retry (allowed up to three tries).
          // Update your web UI if need to inform users about the failed attempt and the retry option.
        }

        // Example UI callback to listen to users' submission of the phone number.
        const submitPhoneNumber = (phoneNumber) => {
          let input: InstantLinkStartInput = {
            phoneNumber: phoneNumber ?? ''
          }
          resolve(input);
        }

        // Example UI callback to process users' cancelling the Instant Link fallback flow.
        const cancelStartStep = () => {
          reject("User canceled Instant Link flow.");
        }
      } else {
        // When phone number is not required since it was already delivered to the Prove Auth server
        // in the Auth Start request, just call resolve(null).
        resolve(null)
      }
    });
  };

Call the resolve(input: InstantStartInput) method to return the collected phone number to the SDK, where the phoneNumber value is wrapped with InstantStartInput.

If the phone number was already delivered to the Prove server in the Start() call, then resolve(null) should be called to communicate to the SDK that the mobile app has the user's agreement to deliver the Instant Link message to the provided phone number.

Call the reject("some error message") method to communicate to the SDK any issues while trying to obtain the phone number. An error should be reported if the user explicitly cancels the Instant Link transaction or presses the back button to leave the Instant Link start step dialog.

Verify the User Information

Once the user has made any edits to their pre-fill information, you should submit that information to the backend server so the Complete() call can then verify the user information.

// Send request to the backend to verify user information.
async function sendInfo(firstName, lastName) {
  const response = await fetch(backendUrl + "/finish", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      firstName: firstName,
      lastName: lastName,
    }),
  });
  const results = await response.json();
  const rsp = JSON.stringify(results);

  return rsp;
}
// Send request to the backend to verify user information.
async function sendInfo(firstName: string, lastName: string) {
  const response = await fetch(backendUrl + "/finish", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      firstName: firstName,
      lastName: lastName,
    }),
  });
  const results = await response.json();
  const rsp = JSON.stringify(results);

  return rsp;
}

Function Reference

The flow can be initiated with Authenticator.authenticate(), while an instance of Authenticator can be created using AuthenticatorBuilder.build().

The following methods can be used to configure Authenticator before instantiating. All methods return the same instance of AuthenticatorBuilder to allow chaining of the configuration methods.

withAuthFinishStep(step: AuthFinishStep | AuthFinishStepFn): AuthenticatorBuilder

This step customizes the handling of the authentication finish call. The implementation should call the customer's backend to retrieve authentication results. The format of the response is defined by the customer to suit the application needs. This configuration is mandatory and must be specified by the application.

withRole(role: DeviceRole): AuthenticatorBuilder

Sets the authentication role for this device. It can be either Primary or Secondary. The Primary value should be set when the end user is on a mobile device web browser that can be registered directly with Prove system and later authenticated by verifying this registration. On other hand, the Secondary value should be set when the end user is on a desktop web browser, which is authenticated after receiving user feedback on their Primary device.

withMobileAuthImplementation(implementation: MobileAuthImplementation): AuthenticatorBuilder

Sets the implementation type for Mobile Auth authenticator. Possible values are Fetch or Pixel with Fetch set by default.

withDeviceIpAddress(deviceIp: string | (() => string | null) | null): AuthenticatorBuilder

Sets the public IP address for this device to be reported during device registration. If this method is not called, or the IP address value is null, the system will attempt to auto-detect the IP address using either an external service or, if not accessible, using the client's IP address of the HTTP connection. The client's public IP address is required for the successful Mobile Auth authentication.

withOtpFallback(startStep: OtpStartStep | OtpStartStepFn, finishStep: OtpFinishStep | OtpFinishStepFn): AuthenticatorBuilder

Configure start and finish handlers for SMS OTP authenticator. These handlers are required for collecting user input to enter the phone number where OTP codes will be delivered, and to enter received OTP codes.

withInstantLinkFallback(startStep: OtpStartStep | OtpStartStepFn): AuthenticatorBuilder

Configure handler for Instant Link authenticator. This handler is required for collecting user input to enter the phone number where Instant Link will be delivered.

build(): Authenticator

Finalizes the configuration and returns a new a instance of the Authenticator.