WebAuthn quick start: Web SDK

Get started with WebAuthn APIs using our Platform SDK. This describes how to quickly integrate WebAuthn registration and authentication into your application.

Check out our sample app

Note

This guide describes integrating using the new Platform SDK. If you've already integrated using the older WebAuthn SDK, you can find that guide here.

Sample flows

Here are samples of some flows that implement WebAuthn biometric login using our Platform SDK. These are simplified representations to help you get to know Transmit Security APIs in context. There are many different ways to implement this and this is just an example.

New credential registration

This flow represents a user registering new WebAuthn credentials. Transmit APIs are shown in pink along with the relevant integration steps.

After initializing the SDK, the client validates that the device supports WebAuthn. Once the user is ready to register their credential, the SDK tells the browser to create credentials, which may require the user to verify (e.g., via TouchID). Once completed, the user's credential is registered and can be used going forward to log in.

Single device authentication

This flow represents an existing user authenticating using registered credentials which exist on the device. Transmit APIs are shown in pink along with the relevant integration steps.

The SDK is initialized. Once the user is ready to authenticate, the SDK tells the browser to get credentials, which may require the user to verify (e.g., via TouchID). Once completed, a user access token is returned to your client backend and the user is logged in.

Step 1: Configure your app

To integrate with Transmit, you'll need an application. From the Applications page, create a new application or use an existing one. The application settings provide client credentials that you'll need for API calls. These credentials are automatically generated when your application is created.

Note
  • Redirect URI is required, although it's not used in the WebAuthn flow. If you're not using any other login method (which do require redirection), enter your website URL here instead. Use HTTPS (e.g., https://login.example.com ), unless using a local environment (e.g., http://localhost ).
  • Enable public sign-up if you manage users using an external system (e.g., external identity provider) or if you want to quickly test WebAuthn registration and authentication without first logging in using a different authentication method.

Step 2: Configure auth method

From the Authentication methods page, configure the WebAuthn login method for your application. For Relying party ID, add your application's domain (e.g., example.com). This is the domain to which WebAuthn credentials are registered and used to authenticate. By default, Transmit will accept any origin that matches this domain, which includes all subdomains.

NOTE

To restrict usage to specific subdomains, toggle the Relying Party Origins and add one or more subdomains of the Relying Party ID to the Web Origins. Ensure to provide the full URL.

TIP

For testing and development, configure the login method for your local environment by setting the RP ID to localhost and the RP origin to http://localhost:(PORT) (e.g., http://localhost:1234).

Step 3: Initialize the SDK

You can install the Platform SDK by adding a script tag to the relevant pages of your application:

Copy
Copied
<!-- Load the latest SDK version within 1.6 range
See changelog for details and update version if necessary -->
<script type="text/javascript"  src="https://platform-websdk.transmitsecurity.io/platform-websdk/1.6.x/ts-platform-websdk.js" defer="true" id="ts-platform-script"></script>

Configure the SDK by calling the initialize() SDK method, passing the client ID obtained in Step 1. For example:

Copy
Copied
await window.tsPlatform.initialize({
    clientId: '[CLIENT_ID]',
    webauthn: {serverPath: 'https://api.transmitsecurity.io'}
});
Note

Configure the SDK to work with a different cluster by setting serverPath to https://api.eu.transmitsecurity.io (for EU) or https://api.ca.transmitsecurity.io for (CA).

Step 4: Register credentials

Before users can login with WebAuthn, they'll need to register WebAuthn credentials. This assumes that the registration flow occurs only after the end-user has logged in to your website (or at least performed an authentication process that verifies their identity), regardless of whether this is done using another Transmit authentication method or using an authentication external to Transmit. Credentials can be registered for existing Transmit users or for new Transmit users.

To implement a WebAuthn registration flow, you'll need to:

  1. Check that the device supports WebAuthn
  2. Register the credentials on the device
  3. Register the credentials for the Transmit user

1. Check for WebAuthn support

Before initiating WebAuthn registration, use the isPlatformAuthenticatorSupported() SDK call to check if the device supports WebAuthn.

Copy
Copied
const isBiometricsSupported = await window.tsPlatform.webauthn.isPlatformAuthenticatorSupported();

2. Register credential on device

When the end-user requests to register biometrics, call the register() SDK call as shown below. Username is required and additional optional properties like display name are supported. This will prompt the user for biometrics. If successful, it returns a result encoded as a base64 string, which is required to complete the registration via your backend.

Note

USERNAME is a verified username, which should be a representation of the user in your system (such as their email, phone number, or another verifiable username known to the user like account ID).

Copy
Copied
// Registers WebAuthn credentials on the device and returns an encoded result
// that should be passed to your backend to complete the registration flow
const encodedResult = await window.tsPlatform.webauthn.register('[USERNAME]');

3. Complete registration

Once WebAuthn credentials have been registered on the device (via the SDK), they will need to be registered in Transmit to the relevant user. This is done by taking the encodedResult parameter returned by the SDK (in step 4.2) and passing it to a backend API. This step varies based on whether the end-user logged into your website using a Transmit authentication method or using an external one, as described below.

User is logged-in via Transmit

Complete WebAuthn credential registration for a user that is currently logged-in using a different Transmit authentication method. This registration API must be called from the backend using the user access token returned upon login. If successful, the credential will be registered for the user that corresponds to the authorization token.

Copy
Copied
const resp = await fetch(
  `https://api.transmitsecurity.io/cis/v1/auth/webauthn/register`, // Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: 'Bearer [TOKEN]' // User access token from Transmit login
    },
    body: JSON.stringify({
      webauthn_encoded_result: '[ENCODED_RESULT]' // Returned by register() SDK call
    })
  }
);

const data = await resp.json();
console.log(data);
User isn't logged-in via Transmit

Complete WebAuthn credential registration for an end-user that logged into your website using a non-Transmit authentication method. This registration API must be called from the backend using a client access token (See Get client access tokens). If successful, the credentials will be registered for the user corresponding to the external user ID in the request. If no user is found, a new user will be created.

Note
  • While an end-user may be logged-in to your website, if they didn't perform a Transmit authentication (i.e., there is no active Transmit session for the user), they are considered a logged-out Transmit user.
  • While an end-user may be registered to your website, if they've never logged in via Transmit (or created as a Transmit user), they are considered an existing user in your system but a new user in Transmit.
Copy
Copied
const resp = await fetch(
  `https://api.transmitsecurity.io/cis/v1/auth/webauthn/external/register`, // Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: 'Bearer [TOKEN]'// Client access token
    },
    body: JSON.stringify({
      webauthn_encoded_result: '[ENCODED_RESULT]', // Returned by register() SDK call
      external_user_id: '[EXTERNAL_USER_ID]'// Identifier of the user in your system
    })
  }
);

const data = await resp.json();
console.log(data);

Step 5: Authenticate user

Once WebAuthn credentials are registered for a user, they can use them to authenticate. Users are identified by their username, which can either be passed in the authentication request or can be inferred from the credential selected by the user. Once authentication is requested, a credential selection list is displayed to the user. You can choose to display this list as a modal native to the browser or using an autofill experience.

To implement a WebAuthn authentication flow, you'll need to:

  1. Authenticate credentials on device
  2. Complete the Transmit authentication to obtain user tokens

1. Authenticate on device

WebAuthn APIs offer two distinct flows to lead to passkey authentication: the modal passkey credential flow, where users to manually enter identifiers, and the autofill passkey credential flow, that embeds passkey authentication into username collection UI fields for a seamless authentication experience.

Note

Steps below describe how to implement authentication using a modal experience for selecting credentials. To add support for autofill, see Next Steps.

To initiate the modal passkey credential flow, use the authenticate.modal() SDK call. The username parameter is optional as it's only used to fetch credentials matching to the username. If the username is not provided, all the credentials for this app in this device will be displayed.

Copy
Copied
// Authenticates WebAuthn credentials on the device and returns an encoded result
// that should be passed to your backend to complete the authentication flow
const webauthnEncodedResult = await window.tsPlatform.webauthn.authenticate.modal("USERNAME"); // Optional username

2. Complete authentication

Once the user has authenticated on the device via the SDK, call the authentication endpoint via your backend with the encodedResult parameter returned by the SDK (in step 5.1) and a client access token (See Get client access tokens).

If successful, ID and access tokens will be returned. The user tokens will identify the user via user_id. If set for the user, it will also include any other verified alias (such as email or phone number) and the external user ID (external_user_id). These tokens should be validated as described here. For more, see the Token Reference.

Copy
Copied
const resp = await fetch(
  `https://api.transmitsecurity.io/cis/v1/auth/webauthn/authenticate`, // Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: 'Bearer [TOKEN]' // Client access token
    },
    body: JSON.stringify({
      webauthn_encoded_result: '[ENCODED_RESULT]' // Returned by authenticate.modal() SDK call
    })
  }
);

const data = await resp.json();
console.log(data);

Full code examples

Here's an example of a registration flow for a logged-in Transmit user:

Copy
Copied
<html>
  <head>
    <!-- Load the latest SDK version within 1.6 range
    See changelog for details and update version if necessary -->
    <script type="text/javascript" src="https://platform-websdk.transmitsecurity.io/platform-websdk/1.6.x/ts-platform-websdk.js" defer="true" id="ts-platform-script"></script>
    <script>
      // Set init params
      const config = {
        clientId: "CLIENT_ID",
        serverPath: "https://api.transmitsecurity.io", // Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA
      }

      async function init() {
        // Check if WebAuthn is supported on the current device
        if (!window.tsPlatform.webauthn.isPlatformAuthenticatorSupported()) {
          alert("WebAuthn is not supported on the current device");
        }
      }

      async function onRegister() {
        // Collect user input
        const username = document.getElementById("username").value;

        // Initialize the SDK
        console.log("Initializing SDK");
        await window.tsPlatform.initialize({
          clientId: config.clientId,
          webauthn: {serverPath: config.serverPath}
        });

        // Register the credential on the device
        console.log("Registering the credential on the device");
        const webauthnEncodedResult = await window.tsPlatform.webauthn.register(username);

        // Send registration result to backend to complete registration with Transmit
        console.log("Registering the credential in TransmitSecurity");
        await fetch("https://YOUR_BACKEND/webauthn/register", {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({webauthnEncodedResult})
        });

        alert(`User ${username} registered successfully!`);
      }
    </script>
  </head>
  <body onload="init()">
    <div>
      Username<br/>
      <input id="username"/>
    </div>
    <button id="register" onclick="onRegister()">Register WebAuthn</button>
  </body>
</html>

Here's an example of an authentication flow using a modal experience:

Copy
Copied
<html>
  <head>
    <!-- Load the latest SDK version within 1.6 range
    See changelog for details and update version if necessary -->
    <script type="text/javascript" src="https://platform-websdk.transmitsecurity.io/platform-websdk/1.6.x/ts-platform-websdk.js" defer="true" id="ts-platform-script"></script>
    <script>
      // Set init params
      const config = {
        clientId: "CLIENT_ID",
        serverPath: "https://api.transmitsecurity.io", // Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA
      }

      // Assumes device is already registered so no need to check for WebAuthn support
      async function onAuthenticate() {
        // Collect user input
        const username = document.getElementById("username").value;

        // Initialize the SDK
        console.log("Initializing SDK");
        await window.tsPlatform.initialize({
          clientId: config.clientId,
          webauthn: {serverPath: config.serverPath}
        });

        // Perform biometric authentication on device
        console.log("Display available credentials and sign the challenge");
        const webauthnEncodedResult = await window.tsPlatform.webauthn.authenticate.modal(username);

        // Send authentication result to backend to obtains user tokens from Transmit
        console.log("Finish authentication and fetch the access token");
        await fetch("https://YOUR_BACKEND/webauthn/authenticate", {
          method: 'POST',
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({webauthnEncodedResult})
        });

        alert(`User ${username} logged in successfully!`);
      }
    </script>
  </head>
  <body>
    <div>
      Username<br/>
      <input id="username"/>
    </div>
    <button id="authenticate" onclick="onAuthenticate()">Login</button>
  </body>
</html>

Next steps

For an even more seamless authentication, you can opt to enable autofill passkey credential flows. Follow these steps:

  1. Initiate autofill flow
  2. Enable autofill in the UI
  3. Abort autofill requests

1. Initiate autofill flow

A prerequisite for enabling passkey autofill is browser support. When loading the page, it is necessary to run the following function that verifies if autofill is supported.

Copy
Copied
const isAutofillSupported = await window.tsPlatform.webauthn.isAutofillSupported();
Note

To handle scenarios where the browser does not support passkey autofill, to ensure passkey authentication, enable the execution of the modal passkey credential flow.

In case of successful response, initiate the authenticate.autofill.activate(handlers) function to allow passkey credential autofill. The handlers parameter is mandatory as it's used to invoke the success/failure handlers after completing the authentication. The username parameter is optional as it's only used to fetch credentials matching to the username. If no username is provided, authentication occurs without relying on any user information, and credentials are automatically inferred from the passkey entered by the user.

Copy
Copied
// Activate the passkey autofill
window.tsPlatform.webauthn.authenticate.autofill.activate({
  onSuccess: handleSuccessfulPasskeyValidation, // Handle successful authentication
  onError: handleAutofillError, // Handle error or passkey cancellation
});

  async function handleSuccessfulPasskeyValidation({webauthnEncodedResult}) {
     // Add code here that sends the encoded result to your backend to complete the authentication flow
  }

  async function handleAutofillError(error) {
    if (error.errorCode === 'autofill_authentication_aborted') return; // Authentication canceled by user
    console.log(error);
  }

2. Enable autofill in the UI

After activating passkey credential autofill, ensure to enable the autocomplete="webauthn" option on your user input field. Here's an example.

Copy
Copied
   <input type="text" size="40" placeholder="user@example.com" autocomplete="username webauthn" />

3. Abort autofill requests

Enabling autofill on your login page corresponds to initiating a WebAuthn authentication flow server-side. Consequently, the registration of passkey credentials and any other login methods will result in an error. If you have incorporated passkey registration or other login flows into the same page, ensure to deactivate (abort) autofill for each specific UI element that trigger those flows (e.g., "Register a new passkey" or "Login with OTP" buttons).

Copy
Copied
// Authenticate user with email OTP
async function verifyOtp(email, otpCode) {
  
  // Abort login page autofill, in case of successful login and redirect
  await window.tsPlatform.webauthn.authenticate.autofill.abort();

  try {
    // Send the OTP code and email to the server...
    }
Note

In the event of authentication failure with other login methods, consider reactivating the autofill to make the option available again and ensure a more accommodating experience for users.