WebAuthn quick start: Android SDK

Add strong authentication with Passkeys to your native Android application, while providing a native experience. This describes how to use the Android SDK to register credentials and use them to authenticate for login.

How it works

This SDK implements Google's Credential Manager API for passkeys. It allows you to add passkeys-based biometric authentication to your Android app, while providing your users with a native experience instead of a browser-based one. With passkeys, credentials are securely stored by the device in Google Password Manager. These credentials must be associated with your domain, so they can be shared between your mobile app and your website (if you have one).

Although the SDK implements our client-side WebAuthn APIs, it offers advantages over the APIs, including:

  • Client-side WebAuthn calls, along with data processing before and after
  • Simplified calls to the Transmit Service, reducing unnecessary complexity

Requirements

The requirements for passkey authentication include:

  • Android 9+
  • Device with registered biometrics (e.g., Face or Fingerprint)
  • compileSdk 34 and onwards
  • minSdk 23

The SDK is built with certain settings and incorporates additional libraries which may require conformance on the hosting application build environment (e.g., Retrofit libraries are usually required to have a conforming or even the same revision for all artifacts integrated into the same application). Below is a specification of these settings:

  • androidx.core:core-ktx:1.8.0
  • androidx.appcompat:appcompat:1.5.1
  • com.squareup.retrofit2:converter-gson:2.9.0
  • com.squareup.retrofit2:retrofit:2.9.0
  • androidx.annotation:annotation:1.5.0
  • androidx.credentials:credentials:1.2.0
  • androidx.credentials:credentials-play-services-auth:1.2.0
  • androidx.biometric:biometric:1.1.0

Sample flows

The sample flows demonstrate possible ways to implement biometric authentication using Transmit Android SDK. In the diagrams below, Transmit APIs are shown in pink along with the relevant integration step.

Registration

This flow represents a new user logging in from a device that supports passkeys. It assumes that you've collected and verified the username before initiating registration. The username 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).

After initializing the SDK, the client validates that the platform authenticator is available. Once the user is ready to register their credentials, the SDK tells the device to create credentials, which may require the user to verify (e.g., via device biometrics). Once completed, the encoded Webauthn result is returned to the client to complete the registration process via their backend. Once registration is completed, the user's credentials are registered and can be used for subsequent logins.

Authentication

This flow represents an existing user authenticating using registered credentials which exist on the device.

The SDK is initialized. Once the user is ready to authenticate, the SDK tells the device to get credentials, which may require the user to verify (e.g., via device biometrics). The SDK returns an encoded WebAuthn result to the client to complete the authentication process via their backend. 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 to configure an application. From the Applications page, create a new application or use an existing one.

From the application settings:

  • For Client type , select native
  • For Redirect URI , enter your website URL. This is a mandatory field, but it isn't used for this flow.
  • Obtain your client ID and secret for API calls, which are autogenerated upon app creation.
  • 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 full website's domain (e.g., www.example.com). This is the domain that will be associated with your credentials in Step 3.

To specify Android mobile origins toggle the Relying Party Origins, and for Mobile origins use android:apk-key-hash:YOUR_APK_KEY_HASH, where YOUR_APK_KEY_HASH is your base64 encoded sha256 hash of your apk signing certificate. This is the origin that will be provided when requesting passkeys registration and authentication.

To obtain the apk key hash, you can use one of the following methods:

Command lineAndroid native code
Copy
Copied
# Export the signing certificate in DER format, hash, base64 encode, trim  '=' and url encode

keytool -exportcert -alias <your-keystore-alias> -keystore <your-keystore> | openssl sha256 -binary | openssl base64 | sed 's/=//g'| sed s/\\+/-/g | sed s/\\//_/g | sed -E s/=+$//
Copy
Copied
private String getFacetID(Context aContext) {
   try {
       PackageInfo info = aContext.getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
       byte[] cert = info.signatures[0].toByteArray();
       InputStream input = new ByteArrayInputStream(cert);
       CertificateFactory cf = CertificateFactory.getInstance("X509");
       X509Certificate c = (X509Certificate) cf.generateCertificate(input);
       MessageDigest md = MessageDigest.getInstance("SHA256");
       return "android:apk-key-hash:" +
               Base64.encodeToString(md.digest(c.getEncoded()), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
   }
   catch (PackageManager.NameNotFoundException e) {
       e.printStackTrace();
   }
   catch (CertificateException e) {
       e.printStackTrace();
   }
   catch (NoSuchAlgorithmException e) {
       e.printStackTrace();
   }
   return null;
}

Step 3: Associate your domain

Google requires having a domain associated with the passkeys credentials. This is done by adding a Digital Asset Links JSON file (assetlinks.json) on your website. Learn more

Step 4: Add SDK to your project

Add the following lines in the shared build.gradle file (allprojects scope):

Copy
Copied
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        maven {
            url('https://transmit.jfrog.io/artifactory/transmit-security-gradle-release-local/')
        }
        mavenCentral()
        google()
    }
}

Add the following in the module build.gradle file (project scope):

Copy
Copied
dependencies {
    implementation 'com.ts.sdk:authentication:1.0.+'
}

// Required to compile against Android API level 34 or later
android {
    compileSdk 34
}

Step 5: Initialize the SDK

Initialize using strings.xml configuration (recommended)

To do this, update the strings.xml file in your Application with the following content, where CLIENT_ID is your client ID (obtained in Step 1).

Copy
Copied
<resources>
    <!-- Transmit Security Credentials -->
    <string name="transmit_security_client_id">"CLIENT_ID"</string>
    <string name="transmit_security_base_url">https://api.transmitsecurity.io/</string>
</resources>
Note

The SDK can be configured to work with a different cluster by setting transmit_security_base_url to https://api.eu.transmitsecurity.io/ (for EU) or https://api.ca.transmitsecurity.io/ (for Canada).

Add the code below to your Application Class

KotlinJava
Copy
Copied
class YourApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        TSAuthentication.initializeSDK(this)
    }
}
Copy
Copied
public class YourApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        TSAuthentication.initializeSDK(this)

    }
}
Initialize using SDK parameters

Configure the SDK by calling the initialize() SDK method using a snippet like the one below, where CLIENT_ID is your client ID (obtained in Step 1), and BASE_SERVER_URL is your base server url, if not provided the SDK will use the default url, which is "https://api.transmitsecurity.io/".

Copy
Copied
class YourApplication: Application() {
   override fun onCreate() {
       super.onCreate()
       TSAuthentication.initialize(this,
            CLIENT_ID,
            BASE_SERVER_URL
       )
   }
}

Step 6: 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 mobile app (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 registration flow, you'll need to:

  1. Check that the device supports WebAuthn
  2. Register a credential on the device
  3. Register the credential in Transmit

1. Check for WebAuthn support

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

Copy
Copied
val isWebAuthnSupported = TSAuthentication.isWebAuthnSupported()

2. Register credentials on device

When the end-user requests to register biometrics, call the registerWebAuthn() 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
TSAuthentication.registerWebAuthn(context, userName, displayName, object: TSAuthCallback<RegistrationResult, TSWebAuthnRegistrationError> {
   override fun success(registrationResult: RegistrationResult) {
       val encodedResult = registrationResult.result()
   }

   override fun error(error: TSWebAuthnRegistrationError) {
       //handle error
   }
})

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 result parameter returned by the SDK (in step 6.2) and passing it to a backend API. This step varies based on whether the end-user logged into your app 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 app 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 app, 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 app, 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 7: Authenticate user

Once WebAuthn credentials are registered for a user, they can use them to authenticate. Users are identified by their username.

To implement an authentication flow, you'll need to:

  1. Authenticate credentials on device
  2. Complete the Transmit authentication

1. Authenticate on device

When logging in users with registered devices, call the authenticateWebAuthn() SDK method. 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. This will prompt the user for biometrics. If successful, it returns a base64 result which is required to complete the authentication via your backend.

For example:

Copy
Copied
TSAuthentication.authenticateWebAuthn(context, userName,
   object : TSAuthCallback<AuthenticationResult, TSWebAuthnAuthenticationError> {
       override fun success(result: AuthenticationResult) {
          val encodedResult = result.result()
          completeAuthentication(encodedResult)
       }

       override fun error(error: TSWebAuthnAuthenticationError) {
           //handle error
       }
   })

2. Complete authentication

Once the user has authenticated on the device via the SDK, call the authentication endpoint via your backend with the result parameter returned by the SDK (in step 7.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() SDK call
    })
  }
);

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

Next steps

Once you've tested your basic WebAuthn integration, you can complete your implementation. See Implement login scenarios for additional logic, like how to verify user identities before registering credentials.