UnifyID is joining forces with Prove!



I am very proud to announce today that UnifyID is joining forces with Prove!

Ever since we started UnifyID, we knew that we were only solving a small piece of the identity problem. We also knew that there are strong network effects in identity and so those who have a large scale will have an unfair advantage against smaller players.

We have a truly innovative technology that is unique in the market and that wins tons of awards. We are the only behavioral biometrics solution that is accurate enough to actually authenticate the person behind the device without requiring any conscious user action. We have some great customers who love our solution. But our commercial impact was limited because we were only solving a part of the problem. Customers needed to piece together their own identity solution from a variety of best-of-breed solutions, and we missed out on some customer opportunities because some companies would not rely on a startup company for something as core as their identity stack. We did not have the breadth nor the scale to have the impact we could have.

Why Prove?

Identity is a huge market, but solutions can roughly be split into two areas:

  • Identity verification (IDV): Proving who you are and tying that to a real-world identity. Typically performed when you set up your account.
  • Authentication: Making sure you are the same person you were the last time. Typically done every time you log in.

At UnifyID, we have been entirely focused on making the second part (authentication) as seamless as possible. This is because authentication is a major form of user friction that happens all the time. But identity verification is also an important part of identity, especially during user onboarding. And the status quo in identity verification is terrible. Many solutions ask questions based on public records or your credit report (like what address you have lived at or your social security number), or ask you to take a photo of your driver’s license, or enter a credit card number. Knowledge-based factors are increasingly worthless for identity verification because they are hard to keep secret in the era of the data breach. They are also annoying for users.

Prove has the best, most seamless solution in the market for identity verification. It allows you to use your mobile phone number to verify your identity in a seamless way. Prove accesses billions of privacy-enhanced digital signals from many authoritative sources such as the major mobile carriers. In many cases, these signals are used to prevent fraud and don’t require the user to take any additional action, but they can also be used to reduce friction in customer onboarding while also making it more secure through solutions like Prove Pre-fill.

Prove has seen great commercial success with its phone-centric identity solutions with thousands of enterprise customers, and is used by over 500 financial institutions worldwide, including 9 of the top 10 U.S. banks. Prove services over 1 billion phones in 195 countries. The combination of Prove’s existing identity solutions and proven go-to-market scale with UnifyID’s focus on seamless authentication and huge lead in innovation will be a new identity powerhouse that compares very favorably to other solutions.

How did this come about?

During the pandemic, with a sudden need to remotely authenticate both employees and users, and with 90% of organizations seeing a significant increase in cyberattacks, we saw a huge uptick in interest in UnifyID. We got connected with Prove during the pandemic and quickly realized that we shared the same vision. At UnifyID, it has always been about making security more seamless for the user, and we focused on mobile as we always knew that mobile was the future of identity and commerce. Prove is also mobile-first and focused on enabling seamless user experiences.

What now?

Don’t worry! We will continue to provide customers access to all of our resources, including our products, APIs, and Developer Portal, while also expanding our offerings with Prove’s support. Our award-winning PushAuth, GaitAuth, HumanDetect, and BehaviorPrint APIs will all become part of Prove’s product line. PushAuth is a great complement to Prove’s existing SMS-based authentication offerings and is the only passwordless or 2FA solution in the market that incorporates user behavior and environment for building adaptive authentication policies. Our other mobile behavioral biometrics solutions will allow Prove to expand into new markets and use cases, including continuous authentication, fraud detection, contact center authentication, and physical access.

All of UnifyID’s leadership and AI/ML team will be joining Prove. We will continue to invest and expand in the future, and the UnifyID team will become the core of a brand new Silicon Valley-based division of Prove.

We are overjoyed to be joining forces with Prove to solve identity and authentication forever.

My Never-Ending Internship

With my internship at UnifyID coming to an end, it’s a great time to look back and reflect. While it didn’t go according to plan, I don’t think it could have worked out any better.

My internship started February 2020 at UnifyID’s office in Redwood City, CA. I still remember my first day — meeting the team, setting up my desk, gawking at all of the free snacks and drinks. The first few days set a great precedent; from the get-go, I was treated as a valued and contributing member of the team. I was invited and expected to dive straight into the code and start working on features. Awesome! My first big undertaking was improving our PushAuth API written in Go. In so many ways I was in over my head, but I was learning quicker than I ever had before and was loving it.

It wasn’t long, though, before COVID came and turned everything upside down. Due to shelter-in-place orders we all had to start working from home. Within the span of one day my morning commute went from biking to the office to walking to the other side of my Airbnb room. Certainly not what I had planned for, but not all bad. Thanks to Morgan and her car, I was able to bring home all the gear I needed. UnifyID was very understanding as we adjusted to working from home and after a short amount of time we were all back up to full speed.

Although it seemed impossible at first, a new normal arose. I was still learning as much as ever while I helped to build our developer dashboard written in React. Outside of work, I became fast friends with my Airbnb host and every week looked forward to a quarantine special — yoga with Yuliia.

In a normal year, this would be the end of my internship story. I had planned to continue on to a new internship at Microsoft in the summer. However, I was given the opportunity to defer this internship, so I did. I really liked the team at UnifyID and the work I was doing and didn’t want to leave.

Eventually, it was time for me to leave California and go home to Canada. Three bizarre plane rides later, I was back home. However, Canadian law required that I strictly quarantine myself for two weeks. I wound up living in the family trailer in our backyard. To make sure that I had a good internet connection for work, my family ended up stringing 100 feet of ethernet cable through the house and backyard to wire me in. It was quite the tripping hazard. Despite working from a different country, I continued to grow closer to the team and improve my skills.

At the end of the summer it was time for me to go back to school. I definitely thought this was going to be the end of my internship story… but it wasn’t. Apparently, I just couldn’t kick the UnifyID bug. They offered for me to continue working part time with them and I accepted. Working part time over two school semesters certainly had its challenges. But, it was so rewarding. I got to work on all sorts of cool projects, which, among many others, included building a sample mobile app for one of our SDKs and writing an associated blog post.

Now, more than one year later, it is the end of my internship story. Looking back, it’s funny how the entire experience ended up working out. Thank you to Kelly and Mike for their excellent guidance and for putting up with my million and one questions. Thank you to Alan and Morgan for being there every step of the way with me and pushing all of my changes to prod. Thank you to Connie for helping to organize and administer my internship. Thanks to the rest of the team for all the other ways you made my internship great. UnifyID was a fascinating place to work with amazing people, and I’m grateful for my never-ending internship.

Using UnifyID GaitAuth™ with IFTTT Rules

Security for your digital life is more important today than ever, but it can be a real pain. Mobile authentication typically means getting out your phone, unlocking it, opening an app, remembering a password, authenticating in the app. Authentication via biometrics like fingerprints or facial recognition are a step in the right direction, but assume you have an ungloved finger or unmasked face available, among other potential shortcomings. 

UnifyID offers a novel new method of uniquely authenticating using the way you walk. With GaitAuth, an app can provide authenticated user functionality as long as the user is carrying their phone as they walk around. 

In this article, we’ll show how easy it is to integrate GaitAuth into a mobile app and use GaitAuth to authenticate the device’s user before triggering an IFTTT Webhook that could be used for sensitive tasks like home automation and security.

What is GaitAuth?

GaitAuth aims to remove the friction from mobile authentication. 

Anyone who has used a modern mobile device knows that passwords and personally identifiable information are not convenient nor, in practice, all that secure. Multi-factor authentication can help, but many popular authentication factors have drawbacks. For example, codes sent via SMS are susceptible to attacks like SMS spoofing and SIM hijacking.

GaitAuth uses motion-based behavioral biometrics and environmental factors to create a unique digital fingerprint of a mobile device’s user. GaitAuth uses this fingerprint to ensure the user is who they claim to be. This kind of passive authentication means no user disruption and real-time protection.

Even better, the GaitAuth SDK lets you easily incorporate GaitAuth into your mobile apps to provide machine learning-powered authentication. 

How does IFTTT fit in?

IFTTT is a popular automation platform that enables even non-technical users to create automations. Using IFTTT, it’s easy to set up workflows that perform actions in one or more services based on events triggered by a device or service. 

For example, a user with a Ring doorbell could use IFTTT to turn on their house’s interior smart lights if the doorbell detects motion in front of the house. But when manipulating user data and working with home automation devices like smart locks, it’s important to verify the user’s identity. 

For example, what if you could unlock a smart lock for your child just by having them walk up to the door? You want to be certain it’s your child walking up to the door before unlocking it. GaitAuth can provide that certainty. 

Getting started with GaitAuth and iOS

Let’s take a look at how you might use GaitAuth to authenticate IFTTT automations in an iOS app. Before you start, make sure you have CocoaPods installed. It’s the preferred installation method for both the GaitAuth and IFTTT iOS SDKs. 

You’ll also need to sign up for a UnifyID developer account so you can obtain an SDK key.

Start by opening Xcode and creating a new iOS app. To keep things straightforward, create a single-view iOS app using Storyboards.

Next, set up a Podfile in your app’s Xcode project directory, and follow the instructions for installing the GaitAuth and IFTTT dependencies. You’ll end up with a Podfile like this:

target 'GaitAuthIFTTT' do
    pod 'UnifyID/GaitAuth'
    pod 'IFTTTConnectSDK'

# Enable library evolution support on all dependent projects.
post_install do |pi|
    pi.pods_project.targets.each do |t|
        t.build_configurations.each do |config|
          config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'

Run pod install from the terminal to install both SDKs. Now add the code needed to set up and use GaitAuth. Start by adding this line to the top of AppDelegate.swift:

import UnifyID

Then, initialize UnifyID just inside the AppDelegate class:

let unifyid : UnifyID = { try! UnifyID(
    sdkKey: "https://xxx@config.unify.id",
    user: "unique-immutable-user-identifier"

You can generate a real SDK key in the UnifyID developer portal. The user attribute can be set to anything you’d like, as long as no two users of your app have the same identifier. Once you’ve chosen an identifier for a user, use the same value every time that person uses your app.

Using GaitAuth in your iOS apps

The first step in using GaitAuth is creating and training a model. When training is complete, GaitAuth uses this model to identify and authenticate the user of the device. 

To train a GaitAuth model, we’ll use the following steps:

  1. Create a model on an iOS device.
  2. Add features to the model. Features represent data about the way the iOS device’s user walks. We’ll need to gather this data to train a GaitAuth model.
  3. Enqueue the model for server-side training.
  4. Check the status of the model on the server until it is ready.
  5. Download the trained model from the server to the device where your app is running.
  6. Use the trained model in your app to score newly collected features — which GaitAuth does automatically as the user walks around while carrying the device. This lets you authenticate quickly and easily.

Let’s examine how this process looks in Swift. To start using GaitAuth in your app, obtain an instance of it by calling:

let gaitAuth = unifyid.gaitAuth

Before we can effectively use GaitAuth in an iOS app, we’ll have to train a GaitAuth model to learn about your app user’s gait. Next, we create a model:

gaitAuth.createModel { result in
    switch result {
    case .success(let gaitModel):
        // Save gaitModel.id
    case .failure(let error):
        // Handle the error

It’s important to save the model ID so we’ll be able to re-load this model from the server if necessary. With the model created, we can start gathering data about the device user’s gait:

gaitAuth.startFeatureUpdates(to: DispatchQueue.main) { result in
    switch result {
    case .success(let features):
        // Called when feature collection is complete
    case .failure(let error):
        // Handle the error

Seven days’ training time is optimal. When the app has gathered enough feature data, call:


This will call the .success result handler, and the features will be ready to use. Note that you need to use an iOS background mode to ensure model training continues even when the app isn’t currently on-screen. If this isn’t possible, use the feature serialization functionality described in the GaitAuth documentation to save the feature data that’s been gathered. This lets the app add to the existing feature collection when execution resumes. 

Next, we add features to the model:

gaitModel.add(features) { error in
    if let error = error {
        // Handle the error
    // Successfully added gait features to model

…and start the training process:

gaitModel.train() { error in
    if let error = error {
        // Handle the error
    // Training is in progress. Notify the user if necessary.

Training occurs on the GaitAuth server, and can take a few hours. When training is complete, the model’s .status property will be ‘ready‘, so the app should periodically check the status to determine when the model is ready to use for authentication.

Setting up an IFTTT Webhook

Now we’ll set up an IFTTT Webhook. Before proceeding, create a free IFTTT account. 

Start by opening the IFTTT dashboard at https://ifttt.com/home. Then, click ‘Create‘ to add a new Applet:

A picture containing text

Description automatically generated

Then, add an ‘If This’:

A picture containing text, clipart

Description automatically generated

Search for Webhooks and select it:

Graphical user interface, application, Teams

Description automatically generated

And choose ‘Receive a web request’:

Graphical user interface, text, application

Description automatically generated

Enter an event name like ‘gaitauth_trigger‘, and click ‘Create Trigger‘. Finally, add a ‘Then That’ to determine what happens when the webhook is called:

A picture containing icon

Description automatically generated

The task chosen here depends on what action we want the GaitAuth-enabled app to trigger. For example, if the device user has a smart lock and wants to unlock their door once they’ve been authenticated by GaitAuth, they can set up a ‘Then That’ to do exactly that. 

This would be particularly useful in a scenario where a parent would like to automatically unlock a house door when their child is coming home from school and approaches the house. There would be no need to remember to bring a key, and there would be no chance of getting locked out. The app, running in the background, can determine when the child is near home, use GaitAuth to verify that the device is being carried by the right person, and call the IFTTT Webhook to unlock the door.

The included sample app triggers an IFTTT Webhook when the device enters a geofenced area surrounding a user-provided address, but it only does so if GaitAuth authentication is successful. It can be used to perform any action that IFTTT is able to trigger – including unlocking a door when a child is nearly home as described above.

Next Steps

Congratulations, you’ve completed our high-level look at how to use GaitAuth and IFTTT together in an iOS app, with a few deeper dives into code at key points. To see what you’ve learned in action, we’ve created a complete sample app that you can find at https://github.com/UnifyID/blog-ios-ifttt

This is just the beginning! While GaitAuth and IFTTT make a great team, just imagine all the places where your iOS apps could benefit from extremely accurate real time authentication based on behavioral biometrics. 

Sign up for a UnifyID developer account at https://developer.unify.id/ and start building ML-powered gait authentication into your apps today.

Unlocking a Password Vault Based on Who You Are, Not What You Have

Security for your digital life is more important than ever. Passwords have been common up until now, but identity and authentication methods are moving away from usernames and passwords. They require users to sacrifice convenience for security and add friction to the overall experience — and many people choose convenience over security due to that friction. 

For instance, if the same password is used for more than one system, then the password being compromised on one system could make the user vulnerable on other systems. 

Password managers, vaults, and multi-factor authentication (MFA) can be useful tools, but much of the time we’re just adding another layer of passwords to remember. Sending authentication codes via SMS or email has been used as an element of MFA processes, but it relies on “what you have” — a device, an email address, or a phone number — and is subject to spoofing or redirection attacks. 

What if we could be even more specific about authentication? Not just what you have, but who you are?

Unique attributes of each person include various aspects of their biometrics, such as their fingerprints or face. While unique, these identifiers might not be available in environments where the user must wear a mask or gloves. Not all devices have biometric support. 

Another way to ensure a person is who they claim to be is by making use of their behavioral biometrics and environmental factors. This type of authentication is passive, or implicit, meaning a user can be authenticated without disruption. It’s also continuous. As a user moves around, the application is able to detect from one moment to the next whether the person carrying the device is the intended user. 

UnifyID offers a novel method of uniquely authenticating using the way you walk. With GaitAuth, your application can provide authentication just by the user carrying a phone as they walk around. In this article, we will show how easy it is to integrate GaitAuth into an Android application.

GaitAuth in an Android App

The GaitAuth SDK lets you easily incorporate machine learning-powered gait authentication into your applications. 

For this article, I created an example of a vault application that stores secrets intended to be accessed by only one user. Since we want to focus on the ease of integrating GaitAuth, the sample just stores simple text secrets like passwords, key strings, or other confidential data. But it could be easily extended to provide one-time passwords (OTP) using a Time-based One-time Password (TOTP) or HMAC-based One-time Password  (HOTP) algorithm. 

The application presents someone with a password challenge, and once the app authenticates the user from either gait data or a correct password, it displays the secrets.

Before integrating GaithAuth into an application, you will need to sign up for a free developer account. Once you have created an account, you will have access to the Developer Dashboard. 

On the Dashboard, next to SDK Keys, click Create. You’ll be asked for a name for identifying the key. Enter the name of your application or some other meaningful label here. After a name is entered and saved, the SDK key will display on the dashboard. You’ll need this key in your mobile app for SDK initialization. For the sample code accompanying this article, place your key in the Android resource file values/secrets.xml into the item named sdkKey.

To add the SDK to the application, a few lines must be added to the project and application build.gradle file. For the project build.gradle, mavenCentral must be added to the buildscript and allprojects sections.

buildscript {
   repositories {
      mavenCentral() // add this line

allprojects {
   repositories {
      mavenCentral() // add this line

In the build.gradle for the module, a line must be added to the dependencies section.

dependencies {
   implementation fileTree(dir: "libs", include: ["*.jar"])
   implementation 'androidx.appcompat:appcompat:1.2.0'
   implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
   implementation 'id.unify.sdk:sdk-gaitauth:1.3.13' // add this line
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test.ext:junit:1.1.2'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

With these changes, a reference to the UnifyID GaitAuth SDK has been added. 

Some permissions are needed that the application might not already have. Within the application’s AndroidManifest.xml file, add the following permissions. 

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

GaitAuth learns how to recognize a user by collecting features from how the person walks. The user should carry the device around for a week so that your application can get to know how the person walks in various situations that may be part of their weekly routine. 

After collecting this feature data, your application will use it to train an ML model for recognizing the user’s steps. A trained model provides a confidence score for the current user being the intended user or for being an imposter. 

Setting Up Feature Collection

The first modification that we want to make to the application is for it to collect features as the user is walking. For collecting features, we place code within an Android service. The service can continue to run and collect features even if the user has navigated to a different application on the device. 

When the service starts, it will initialize the GaitAuth SDK, show a notification to let the user know that it is running, and begin collecting features. 

public int onStartCommand(Intent intent, int flags, int startId) {
    if(!isInitialized) {
        super.onStartCommand(intent, flags, startId);
        // The username is passed from the activity that starts the service
        userID = intent.getStringExtra(INTENT_USER_ID);
        isInitialized = true;
    return START_NOT_STICKY;

void initGait() {
   UnifyID.initialize(getApplicationContext(), GetSDKKey(), userID, new CompletionHandler() {
    public void onCompletion(UnifyIDConfig config) {
        GaitAuth.initialize(getApplicationContext(), config);
    public void onFailure(UnifyIDException e) {
        Log.e(TAG, e.getMessage());

The gait model object is used for recognizing a user by the features of their steps. When a gait model is created, it receives a unique ID string. The ID string should be saved. The ID string can be used to reload the model when the application restarts later. 

void initModel() {
   // If we have not already instantiated a GaitModel, then create one.
    if(gaitModel == null ) {
        // See if there is a GaitModel ID that we can load.
        SharedPreferences pref = getSharedPreferences(
        String modelID = pref.getString(PREFERENCE_KEY_MODELID,"");
        GaitAuth gaitAuth = GaitAuth.getInstance();
        try {
            // If there is no modelID, then create a model and save its ID
            if (modelID == "") {
                gaitModel = gaitAuth.createModel();
                SharedPreferences.Editor editor = pref.edit();
                editor.putString(PREFERENCE_KEY_MODELID, gaitModel.getId());
            } else {
                // If there is a modelID, then use it to load the model
                gaitModel = gaitAuth.loadModel(modelID);
        } catch (GaitModelException exc) {

Training the Model

Since the model hasn’t been trained, it is not able to recognize the user yet. Let’s collect some features to train the model. As new features are generated, they are added to a collection. When the collection reaches a prescribed size, it is saved to device storage. 

final int FEATURES_TO_HOLD = 250;
Vector<GaitFeature> gaitFeatureList = new Vector<GaitFeature>();

void startFeatureCollection() {
    try {
        GaitAuth.getInstance().registerListener(new FeatureEventListener() {
            public void onNewFeature(GaitFeature feature) {
                if(gaitFeatureList.size()>=FEATURES_TO_HOLD) {
    } catch (GaitAuthException e) {

The SDK provides methods for serializing and deserializing feature lists to byte data. We will use the static method GaitAuth.serializeFeatures to make a byte stream from the feature list. This byte stream is then written to storage. 

void saveFeatures()  {
    if (gaitFeatureList.size() == 0) {
    Vector<GaitFeature> featuresToSave = new Vector<GaitFeature>();
    synchronized (gaitFeatureList) {
    try {
        byte[] featureData = GaitAuth.serializeFeatures(featuresToSave);
        File storageFile = getStorageFile(getNextFileSegment());
        FileOutputStream fos = new FileOutputStream(storageFile);
        fos.write(featureData, 0, featureData.length);
        notificationBuilder.setContentText(String.format("Saved feature set %d containing %d elements at %s", getFeatureCount(), featuresToSave.size(), new Date()));
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.notify(1, notificationBuilder.build());
    } catch (FeatureCollectionException | FileNotFoundException exc) {
    } catch (IOException exc) {

Once there are enough features, we can initiate training. UnifyID recommends a minimum of three days and 7,000 walk cycles, but suggests using seven days and 10,000 walk cycles for optimal training.

In the sample program, a button on the settings screen starts the training. 

To train the model, we deserialize the features that have been collected and input them into our model using the add method. After the features are added, we call GaitModel.train. Training can take a few hours. GaitModel.getStatus gets the status of the model. The function returns one of the following values:

  • CREATED – the model hasn’t yet been trained.
  • TRAINING – training is in process. Check again later for the training result.
  • READY – the model is trained and ready to begin recognizing users
  • FAILED – the model could not be trained. 

If training fails, GaitModel.getReason returns a string that explains why the failure occurred. Attempting to train the model with a tiny data set results in a message stating that the amount of training data is insufficient. When the function returns READY, we can begin using it to authenticate the user. 

Authenticating the User

For a model that is ready, there are two methods of evaluating this data to determine if a device is in the hands of the intended person. 

One method is to examine the gait feature score. Possible scores range from -1.0 to 1.0. 

Positive scores indicate the current user is the person whose walking patterns were used to train the model. 

Negative scores indicate the person holding the device is an imposter. 

Your organization may want to evaluate different thresholds for acceptance to find one that is acceptable. A passing score of 0.6 to 0.8 is a good starting point. 

In the following code, the scores of the last four features collected are averaged together. If the most recent feature was collected within the past 60 seconds and the average is greater than 0.6, then the user is considered authenticated.

static final float PASSING_SCORE = 0.6f;
static final long  MAX_PASSING_AGE = 30000;
static final int MIN_SCORE_COUNT = 4;

public boolean isAuthenticated() {
    if(gaitScoreList.size()>=MIN_SCORE_COUNT) {
        long age = (new Date()).getTime() - gaitFeatureList.get(gaitFeatureList.size()-1).getEndTimeStamp().getTime();
        float sum = 0.0f;
        for(GaitScore score:gaitScoreList) {
            sum += score.getScore();
        float avg = sum / (float)gaitScoreList.size();
        if(avg > PASSING_SCORE && age <=MAX_PASSING_AGE) {
            return true;
    return false;

When examining the feature scores to authenticate the user, there is freedom for deciding if a user will be authenticated or not for certain scenarios. 

There is also a much easier way to authenticate a user. The GaitAuth SDK provides an authenticator that can collect features and perform authentication for you. 

To create a GaitAuth authenticator, first create a configuration. The configuration contains settings for the maximum age of features to consider, setting a threshold for a passing feature, and other attributes that affect how the features are evaluated.

GaitQuantileConfig config = new GaitQuantileConfig(QUANTILE_THRESHOLD);
config.setMinNumScores(1);    // Require at least 1 score
config.setMaxNumScores(50);   // Set the maximum number of scores to use for authentication
config.setMaxScoreAge(10000); // Set the maximum age, in milliseconds, for features
config.setNumQuantiles(100);  // Set the number of quantiles (divisions) for the feature data

Once created, the authenticator continues to collect information from the user’s walking. The authentication status can be checked at any time by calling getStatus() on the authenticator. The call to getStatus() accepts an  AuthenticationListener. Either the onCompletion or onFailure method on this object will be called. 

Note that if onCompletion is called, that does not imply that the user was authenticated. A call to onCompletion means that the authenticator was able to perform an evaluation. To determine if the user is authentic, check isAuthenticated.

gaitAuthenticator.getStatus(new AuthenticationListener() {
   public void onComplete(AuthenticationResult result) {
       gaitAuthenticatorResult = result.getStatus();
           case AUTHENTICATED:
               isAuthenticated = true;
           case UNAUTHENTICATED:
               isAuthenticated = false;
   public void onFailure(GaitAuthException cause) {


The authenticator will continue to authenticate the user in the background. 

The application unlocks the stored secrets when it detects the user recently walked and that it was an authorized user. If the application cannot authenticate the user from their walk (the user hasn’t recently walked), the password unlock feature is available as a fallback.

Next Steps

We now have a password vault application that is able to recognize a user by the way that they walk. We did this by adding the GaitAuth SDK to the project, collecting features based on the user’s gait, and using the features to train a model to recognize the user. The application unlocks for the user when the trained model recognizes their gait. As noted earlier, we can easily change the application functionality to add features like one-time passwords.

You can read more about the GaitAuth SDK at UnifyID’s documentation site. Sign up for free on the developer dashboard to begin testing with the SDK. For a more in-depth look at how gait biometric verification works, see this publication on the UnifyID site. You can download the sample application used in this blog post from https://github.com/UnifyID/blog-android-secrets.

Identity and Access Management: Trends to Watch

The next few years will be crucial to the development of identity and access management (IAM) trends. As more and more enterprises recognize the value of multi-factor authentication (MFA), emerging trends such as the Internet of Things (IoT), 5G, behavioral biometrics, cybersecurity education, and blockchain will define the future of IAM development.

Emerging Network Technologies

The world is getting smaller and smaller, and it’s all thanks to the IoT. The global networks of smart devices which comprise the IoT pose both benefits and risks to cybersecurity. On one hand, the more streamlined and integrated IoT-enabled devices become, the easier it is to implement MFA layers such as biometrics and push authentication. On the other hand, each added IoT device to a network can turn into an access point for data thieves. And as 5G further streamlines communication between smart networks, the more pronounced these benefits and threats will become.

Motion-Based Behavioral Biometrics

The combined development of sensor technology and data analytics has led to the emergence of motion-based behavioral biometrics. This emerging biometric method authenticates identity based on how a person walks. This is how our GaitAuth™ system works in a nutshell. It allows for passive and continuous authentication, providing seamless but truly unique and secure access management to any type of location, vehicle, or situation. And because this biometric layer is based on not just gait but posture data, it can start authentication based on a person’s static stance. Furthermore, it also works on people wearing face masks and gloves, making it ideal for preventing contamination and not getting in the way in situations where frequent authentication is necessary.

Blockchain and IAM

Decentralized finance (DeFi) and cryptocurrency are just the tip of the blockchain iceberg. Considering the original goal of blockchain which was to provide a decentralized, cryptography-based method of secure transactions, it’s no surprise that the technology will have a close relationship with the ongoing development of IAM. MFA-enabled IAM applications will continue to be crucial to preventing fraud and identity theft in DeFi transactions. Likewise, advancements in cryptography and programming driven by ballooning crypto wallets will drive the next generation of emerging IAM techniques.

Greater Online Access to Cybersecurity Education

The ongoing digital migration in education will also have a crucial part in the evolution of IAM. Nowadays, more and more top universities are getting involved in making cybersecurity education more accessible to different generations and walks of life. For instance, tech company Apex has recently collaborated with the University of Maryland to create an online program for providing professional-level cybersecurity skills to both their own employees as well as military veterans. The recently-minted online cybersecurity training analyst program at the University of Maryland Global Campus is definitely a big step towards producing more professionals who have a practical grasp of IAM, an increasingly in-demand form of expertise in the digital era.

Similarly, the online cybersecurity degree at Maryville University is a program that’s also aimed at responding to that demand. Maryville University’s Virtual Lab allows its undergraduate students to complete 100% online coursework and develop technical analytics and hacking skills in a safe environment – one that’s been distinguished by tech giant Apple for mobile innovation. There’s definitely no shortage of online programs that can afford anyone with a laptop and a web connection, a more in-depth purview of the ever-evolving IAM and cybersecurity landscape.

From software engineers and mid-level managers to the board of directors at tech-dependent companies, this type of education is crucial for anyone who works alongside virtual networks. It’s no secret that many organizations are still lacking in terms of the necessary IAM protocols that can sufficiently protect their networks from hackers. Even relatively simple but effective solutions like push authentication still lacks momentum in terms of implementation. And it’s nearly the same for more complex MFA layers like motion-based behavioral biometrics, blockchain IAM integration, and other emerging technologies. If you don’t want your company to get swept under the rug, these are the IAM trends to watch out for – and implement – in the coming years.

Author: Lindie Sparks
An article for unify.id

Integrating GaitAuth™ into Your Android App

GaitAuth™ is an SDK offered by UnifyID that allows you to authenticate your users based on how they move. This post demonstrates the process of adding GaitAuth to a simple Android app.

The UnifyID Developer Portal has high-level documentation that covers the basics of the GaitAuth SDK integration process. There are also auto-generated JavaDocs available for the specific details. The goal of this post is to meet the two in the middle and give some more color to the integration process. By the end of the post we’ll have built a sample Android app that uses GaitAuth to train and test a GaitModel. Along the way, we’ll also explore relevant best practices and important implementation details.

The Sample App

All of the sample app code is available on GitHub. To get a feel for the training and testing process you can build the app on your phone and try it for yourself. If you want to get right to building your own app, you can treat the repository as a collection of helpful code snippets. You can also use the code to follow along with this post in depth. Not all of the code is shown in this post, the code that is shown has been abbreviated for simplicity’s sake. Links to the original code on GitHub are provided at the top of the snippets.

The sample app closely mirrors the functionality of the GaitAuth SDK. On the starting screen you are presented with the option to create or load a model. You’ll want to create a new model when you first use the app. In future uses, you can use the load option to skip training. After creating a model you’re ready to collect data and train the model. First, turn on collection (it will continue to collect even if the phone is locked or the app is backgrounded or closed). Once you’ve collected some features you can add them to the model. After you’ve collected enough data you can train the model. While you wait for the model to train (which may take a few hours), you can manually refresh its status.

If the model fails to train you’ll need to start over by pressing the trashcan icon in the top left corner. When the model successfully trains you’re ready to test it. Turn on feature collection and walk around for a while. When you are ready, stop collecting features and score them. This will graph the scores and show you an authentication decision.

Now that we know what the sample app does, let’s go build it.

Configuration & Initialization

Adding GaitAuth to our Gradle configuration is a good starting point. We’ll also need to request some permissions in the Android Manifest. We can follow the steps outlined in the Developer Portal documentation to take care of that.

Before the GaitAuth SDK can be used anywhere in the app it must be initialized. The initialization is trivial; always initializing it before using it is harder. In something straightforward like this sample app we can simply initialize it in the onCreate method of the MainActivity.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L144
if (GaitAuth.getInstance() == null) {
    // First initialize the UnifyID Core SDK
    UnifyID.initialize(getApplicationContext, SDK_KEY, USER, new CompletionHandler() {
        public void onCompletion(UnifyIDConfig config) {
            // Second initialize the GaitAuth SDK
            GaitAuth.initialize(context, config);
            // Save these values for debugging purposes
            Preferences.put(Preferences.CLIENT_ID, config.getClientId());
            Preferences.put(Preferences.INSTALL_ID, config.getInstallId());
            Preferences.put(Preferences.CUSTOMER_ID, config.getCustomerId());

When initialization succeeds we squirrel away a couple of relevant details in the Shared Preferences (the Preferences class hides the idiosyncrasies of the Shared Preferences API). These will come in handy for debugging. You can view them by tapping on the info icon in the top right corner of the sample app. We also start a foreground service — more on this later. Finally, we call route which determines what the app does next.

In an app with a more complex workflow, initialization of the GaitAuth SDK may not be so simple. In these scenarios we recommend you wrap the SDK in a class that protects its usage. With some simple synchronization this wrapper can ensure that the SDK is never used uninitialized.

Two important points remain. First, remember that the SDK key is a sensitive value and should be protected. In the sample app we ask the user to enter the SDK key the first time they use the app. For an app in production, something like Android Keystore can be used. Second, the user value has a few important stipulations on it. It must be unique, immutable, and should be unrelated to any personally identifiable information. We don’t recommend using things like email addresses or phone numbers for the user value.

The GaitModel

Now that we’ve gotten through the drudgery of initialization it’s time to talk about the star of the show — the GaitModel. In a sense the sample app can be viewed as a tool for managing the lifecycle of a GaitModel. It creates a new model, loads training data into it, initiates training, and then tests the model. For this reason, the sample app’s routing is based on the status of the current GaitModel.

The sample app uses a single-activity/multiple-fragment architecture where each fragment is a different screen. In the MainActivity after the initialization of the GaitAuth SDK the following routing code is run.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L185
private void route() {
    // Load the id of the current GaitModel
    String modelId = Preferences.getString(Preferences.MODEL_ID);
    if (Strings.isNullOrEmpty(modelId)) {
        showFragment(new SelectModelFragment());
    } else {

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L287
private void loadModel(String modelId) {
    AsyncTask.execute(() -> {
        model = GaitAuth.getInstance().loadModel(modelId);
        Preferences.put(Preferences.MODEL_ID, modelId);

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L255
private void renderFragmentBasedOnModelStatus(GaitModel.Status status) {
    switch (status) {
        case CREATED:
        case TRAINING:
            showFragment(new ModelPendingFragment());
        case FAILED:
        case READY:
            // treat it as a failure
            showFragment(ModelErrorFragment.newInstance("unknown model status"));

First the method route looks for the id of the current GaitModel and asynchronously loads it with the help of loadModel. After the model is loaded, renderFragmentBasedOnModelStatus is called. A simple switch statement then sends the user to the screen matching the current state of the model.

The method route will not find a model id on a user’s first time through the app or if the reset button was pressed. In these cases the user is immediately sent to the SelectModelFragment. From here, when the user clicks on the “Create Model” button, onCreateModelPressed is executed. It builds a new GaitModel, saves the model id in the shared preferences, and sends the user to the FeatureCollectionFragment. Alternatively, the user can opt to load a pre-existing model which leverages the same loadModel method.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L315
public void onCreateModelPressed() {
    AsyncTask.execute(() -> {
        model = GaitAuth.getInstance().createModel();
        Preferences.put(Preferences.MODEL_ID, model.getId());
        showFragment(new FeatureCollectionFragment());

Feature Collection

So we have a GaitModel locked and loaded, but now what? The model is useless without training data so let’s start there. A GaitModel is trained on a collection of GaitFeatures which are data points collected from the user’s movement. These GaitFeatures are given to the model via the add method which has the signature void GaitModel.add(Collection<GaitFeature> features). Later during training, only the features explicitly added to the model will be used.

Now that we know how to use the features, how do we actually collect them? This is achieved by registering a FeatureEventListener that will fire the onNewFeature callback for every feature collected. Correctly managing feature collection requires tackling two key issues: feature storage and backgrounding.

Storing Features

In a naive implementation of feature collection, every time a new feature was received it would be immediately added to the GaitModel. There are two problems with this. First, adding features to the GaitModel may use network resources or trigger other expensive operations and thus is inefficient to call frequently. Second, if the model fails when training you will need to recollect all new data for the new model since you didn’t persist it anywhere.

The sample app solves the feature storage problem with a thread-safe FeatureStore class. This class exposes a method with the signature void add(GaitFeature feature), which serializes the feature and appends it to a file on disk. At a future time (when the user clicks the “Add Feature” button) all of the features can be loaded from disk, deserialized and returned via the method getAll with the signature List<List<GaitFeature>> getAll(). Note that it returns the features partitioned into multiple lists. This is because adding thousands of features to a model at once should be avoided. Finally, there is an empty method to clear the file after adding features. This prevents adding features twice. Deleting the features after using them reintroduces the persistence problem though. The sample app does it anyways to avoid re-adding features to the model in a simple manner.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/GaitAuthService.java#L163
public void onNewFeature(GaitFeature feature) {
    int count = Preferences.getInt(Preferences.FEATURE_COLLECTED_COUNT) + 1;
    Preferences.put(Preferences.FEATURE_COLLECTED_COUNT, count);

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L363
public int onAddFeaturesPressed() {
    FeatureStore featureStore = FeatureStore.getInstance(this);
    List<List<GaitFeature>> chunks = featureStore.getAll();
    int uploadedCounter = 0;

    for (List<GaitFeature> chunk: chunks) {
        uploadedCounter += chunk.size();
    if (uploadedCounter > 0) { // only truncate file after we uploaded something
    return uploadedCounter;

In a production application there are many improvements you would want to make to FeatureStore. First and foremost, it should implement some form of in-memory buffering. Writing to disk for every feature you collect is slow and can lead to poor battery-life. Second, it should rotate files to avoid size limitations and corruption. Third, it should not clear the file after adding the features to the model. Rather, it should keep track of what features have been added and only add the new ones.


The second key issue to solve for feature collection is backgrounding. It would not be wise to register the FeatureEventListener on the app’s main thread. As soon as the user closed the app, GaitFeatures would no longer be collected. Android services can help us solve this problem. Services provide a way to execute a long-running operation in the background. There are two relevant types of Android services: background and foreground. A background service needn’t provide any indication to the user that it is running, but is more likely to be shut down by the OS if resources are scarce. A foreground service must present a notification to the user indicating its presence the entire time it is alive. But, it is unlikely to be killed by the OS.

The sample app takes the most straightforward approach. In the onCreate method of the MainActivity a foreground service is created regardless of whether or not the service will be used. This avoids complex state management and synchronization issues that arise when dynamically building a service. In the onCreate method the activity binds to the service. And in the onStop method it unbinds from the service. This gives the activity direct access to the feature collection service.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L104
protected void onStop() {
    if (gaitAuthServiceBound.get()) {

You may want to handle services differently in a production app. For example, you may only want to start the service when you are actually collecting features. If you go this route make sure to take special care in synchronizing the usage of the GaitAuth SDK.

Final Considerations

That was a lot of details. Let’s take a step back for a moment and consider the entire training process. Feature collection for training is the most critical stage for the GaitAuth SDK. To get good authentication results you need to have a well trained model. The Developer Portal documentation has a number of suggestions on how to best do this.

Training the Model

The hard work of feature collection was well worth the effort. We now have a plethora of GaitFeatures to train with. We’re nearly ready to use our GaitModel, but first we need to train it. Thankfully, training is easy. When a user clicks on the “Train Model” button the method below is called. In a background thread it kicks off the training process for the model and sends the user to the ModelPendingFragment.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L397
public void onTrainModelPressed() {
    AsyncTask.execute(() -> {
        try {
            showFragment(new ModelPendingFragment());
        } catch (GaitModelException e) {
            showToast("Failed to start training model.");

At the pending model screen a user can manually refresh the status of a model while they wait for it to train.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L436
public void onRefreshPressed() {
    AsyncTask.execute(() -> {
        try {
        } catch (GaitModelException e) {
            showToast("Failed to refresh model status.");

After refreshing the status of the model, our old friend renderFragmentBasedOnModelStatus is called to bring the user to the right screen given the model’s status. If the training failed for some reason the ModelErrorFragment will load. From there the user has no choice other than to reset and start over. After a successful training, the user will be presented with the TestingFragment. And of course, if the model status is still TRAINING after a refresh then no navigation will occur.

Testing the Model

The hard work is over now — we’ve trained a GaitModel and can now reap the benefits. All of the testing functionality is managed by the Authenticator interface. When you instantiate an Authenticator you pass it a GaitModel and a scoring policy. Once created, it automatically starts collecting features. You can ask it for an authentication decision at any time and it will say if the user is authenticated or unauthenticated. In the scenario where this is not enough data to make a decision it will return inconclusive. When the user presses the “Start Collection” button onStartCollectionBtnPressed is called. It in turn tells the foreground service to create a new Authenticator object.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L338
public void onStartCollectionBtnPressed() {
    try {
    } catch (GaitAuthException e) {
        showToast("Failed to start feature collection for testing.");

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/GaitAuthService.java#L213
public void startFeatureCollectionForTesting(GaitModel model) throws GaitAuthException {
    GaitQuantileConfig config = new GaitQuantileConfig(QUANTILE_THRESHOLD);
    authenticator = GaitAuth.getInstance().createAuthenticator(config, model);

A quick aside on the GaitQuantileConfig policy and how it works. In short it will authenticate the user if at least X percent of scores in the session scored Y or more (learn more about scores here). X is known as the quantile and Y is the score threshold. GaitQuantileConfig sets the quantile to 50% by default and in the sample app the score threshold is set to 0.8. The table below shows three example sessions with these numbers. The first session has 60% of scores at or above the 0.8 score threshold, and therefore has an authenticated result. However, the second and third sessions only have 40% and 20% of scores that meet the 0.8 score threshold, and therefore they produce an unauthenticated result.

You can customize more than just the quantile and score threshold of the GaitQuantileConfig. The method setMinNumScores lets you configure how many scores are required for an authentication decision to be made. Any attempt to authenticate with a number of scores less than this minimum will return inconclusive. Similarly, setMaxNumScores configures the maximum amount of scores that will be considered. If there are more scores than the maximum, the most recent scores will be chosen. Finally, setMaxScoreAge determines how old of scores can be used. Scores older than the given age will not be used for an authentication decision.

Back to the action. The sample app requires the user to stop collecting features before they can score them. Note that this is not a requirement of the GaitAuth SDK and is only done to simplify state management. The Authenticator has a stop method which helps us do this. Clicking the “Stop Collection” button kicks off this sequence of events.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L349
public void onStopCollectionBtnPressed() {

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/GaitAuthService.java#L223
public void stopFeatureCollectionForTesting() {
    if (authenticator != null) {

Now the user can press the “Score Features” button. This entails getting the authenticator from the foreground service and then getting an authentication status from the authenticator. Then it sends the user to the ScoreFragment. The authenticator also returns the individual scores that lead to the authentication decision. ScoreFragment puts these in a graph to help visualize the decision. How you use the authenticator will be highly dependent on the specifics of your use case.

// https://github.com/UnifyID/gaitauth-sample-app-android/blob/470932205438dd2ce43dc6bad586df90c72cc800/app/src/main/java/id/unify/gaitauth_sample_app/MainActivity.java#L469
public void onScoreFeaturesPressed() {
    Authenticator authenticator = gaitAuthService.getAuthenticator();
    authenticator.getStatus(new AuthenticationListener() {
        public void onComplete(AuthenticationResult authenticationResult) {

        public void onFailure(GaitAuthException e) {
            showToast("Failed to get scores.");


And just like that we’ve integrated GaitAuth into a simple Android application. For more implementation details you can explore the code in depth on GitHub. If you build something with GaitAuth, let us know on social media. We’d love to hear about it. Finally, reach out to us if you have any trouble integrating.

Integrating Trusted Registration for PushAuth™

Welcome to the final post of the Power of PushAuth™ blog series. In this post, we will enhance the basic website we created in the Building a Web Application with PushAuth™ post by integrating trusted registration to provide a way for the mobile SDK to only register authorized users.

For a detailed explanation on trusted registration, please refer to the following:

In this tutorial, we will integrate trusted registration into a web application so that a user, upon signup, is given a 4 digit “pairing code” they can enter in the mobile app. After this, the mobile client added will be the only one who is able to receive login request push notifications.

The end result of this blog post will be almost identical to the publicly available sample web application used in the previous blog post that introduced trusted registration.


To follow this tutorial, you will need:

This tutorial assumes a basic familiarity with the Rails framework. Also, the starting point of this guide is the Rails app we built in the Building a Web Application with PushAuth™ post. If you have not followed this previous post to build the web application from scratch, you can clone the pushauth-sample-server in our GitHub repository as the starting point. If you choose to use the GitHub project as a starting point, make sure to initialize the project by following the steps in README of the project.

Step 1 – Provide a pairing code for users

Database Setup

First, we need to add new data to the User table in the database to keep track of an integer pairing code and a boolean which tracks whether or not the code has been used.

Let’s generate a migration to add these columns:

$ rails generate migration add_verification_code_to_users verification_code:integer

The newly generated migration should look like this:

# db/migrate/{some_date}_add_verification_code_to_users.rb

class AddVerificationCodeToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :verification_code, :integer

We also want to add a column to keep track of whether each pairing code has been used. This column should default to false so that we don’t have to set it when we create a user. Add this line under the other add_column line from the migration file:

add_column :users, :verification_used, :boolean, default: false

Run bundle exec rails db:migrate, and we should have the appropriate database setup.

User Model

Let’s add some business logic to our User model so that we can ensure the consistency of our table.

We want to add validation to the username such that it must be unique, and we want to add a before_create hook that generates a random verification code for a new User. While we’re at it, we may as well make a function that tries to use up a validation code, and returns whether or not it was a success.

# app/models/user.rb

class User < ApplicationRecord

 validates :username, presence: true, uniqueness: { case_sensitive: false }

 before_create :generate_verification_code

 def consume_verification_code(provided_code)
   if provided_code == self.verification_code && !self.verification_used
     self.update_attributes(verification_used: true)


 def generate_verification_code
   self.verification_code = SecureRandom.rand(1000...9999)

A quick note about consume_verification_code: in absence of any statements following the if, Ruby will return a boolean corresponding to the evaluated condition, so this will return true if and only if the code was correct and had not been used before.

Users Controller and View

Now, let’s make a UsersController so that we can implement user registration:

$ rails generate controller Users

In UsersController, we will add three actions

  • new : a signup form
  • create : the endpoint to which the signup data is sent
  • post_signup : the page displaying the verification code to the user
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  skip_before_action :authorized

  def new
    @user = User.new

  def create
    @user = User.new(params.require(:user).permit(:username, :password))

    if @user.save
      session[:signup_username] = @user.username
      session[:signup_verification_code] = @user.verification_code
      redirect_to post_signup_users_path
      render :new

  def post_signup
    @username = session[:signup_username]
    @pairing_code = session[:signup_verification_code]

Now, let’s add some new views.

app/views/users/new.html.erb for the signup page:

# app/views/users/new.html.erb

<%= form_for @user, class: "form-signin" do |f| %>
<% if @user.errors.any? %>
    <div class="error_messages">
      <h2>Form is invalid</h2>
        <% @user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
  <% end %>
  <%= f.label :username, class: "sr-only" %>
  <%= f.text_field :username, class: "form-control", placeholder: "Username", autofocus: true %>

  <%= f.label :password, class: "sr-only" %>
  <%= f.password_field :password, class: "form-control", placeholder: "Password" %>

  <%= f.submit "Sign up!", {class: ["btn", "btn-lg", "btn-primary", "btn-block"]} %>
<% end %>

app/views/users/post_signup.html.erb displays the pairing code.

# app/views/users/post_signup.html.erb

Thanks for registering! Please enter the following in our app:

  Username: <%= @username %>
  Pairing code: <%= @pairing_code %>

And finally, we add our routes:

# config/routes.rb
# somewhere within the main do...end block

  resource :users, only: [:new, :create] do
    get "post_signup"

We now want to add signup links from a couple different pages:

Replace the Welcome! Please <%= link_to "log in", "login" %>. line in app/views/application/home.html.erb with the following:

Welcome! Please <%= link_to "log in", "login" %> or <%= link_to "sign up", new_users_path %>.

And add this to the bottom of app/views/sessions/new.html.erb:

Don't have an account yet? Sign up <%= link_to "here", new_users_path %>!

At this point, you should be able to test the new signup flow by running bundle exec rails server.

Step 2 – Implement Trusted Registration Webhook Endpoint

When the user enters their name and pairing code in the app, the UnifyID PushAuth™ service will send a POST request to the Rails app for permission to add a device. On the Developer Dashboard, you can set up the endpoint URL. A detailed explanation on how the trusted registration webhook endpoint works can be found in the previous blog post.

We will make an endpoint for user verification at /users/trust, which checks if a given user and a challenge token (i.e., the pairing code for this sample website) match with our database records.

# app/controllers/users_controller.rb
# add these lines right under "class UsersController < ApplicationController"

  skip_before_action :authorized
  skip_before_action :verify_authenticity_token, only: :trust
  http_basic_authenticate_with name: Rails.application.credentials.unifyid[:basic_username],
    password: Rails.application.credentials.unifyid[:basic_password],
    only: :trust
  def trust
    @user = User.find_by(username: params[:user])
    if @user && @user.consume_verification_code(params[:challenge].to_i)
      head :ok
      head :unauthorized

Let’s take a look at these in a bit more detail:

  skip_before_action :verify_authenticity_token, only: :trust

By default, Rails has cross-site request forgery protection for forms, which means that a form that submits data via POST will have an authenticity token that makes it difficult for attackers to manipulate your browser into performing unauthorized requests. You can read more about this before_action here.
For our use case, however, the UnifyID service will not know the proper authenticity token, so we relax that requirement on the trust action.

  http_basic_authenticate_with name: Rails.application.credentials.unifyid[:basic_username],
    password: Rails.application.credentials.unifyid[:basic_password],
    only: :trust

This adds HTTP Basic Authentication to the /user/trust endpoint in order to make sure the request is from the UnifyID PushAuth™ service. Note that you will have to add basic_username and basic_password entries to the Rails application credentials (bundle exec rails credentials:edit).

Finally, we can add our route:

# config/routes.rb

  resource :users, only: [:new, :create] do
    post "trust"

    get "post_signup"

And there you have it! We’ve added user signup, and we have secured mobile client registration to to UnifyID PushAuth™ service. The end result should look like our pushauth-sample-server-reg project in GitHub, which was introduced in the Power of Trusted Registration blog post.

This concludes the Power of PushAuth™ blog series. Thanks for following along, and feel free to reach out to us if you have any questions, comments, or suggestions!

Interview With John Whaley – UnifyID by Safety Detectives

John Whaley: Founder and CEO of UnifyID

Aviva Zacks of Safety Detectives sat down with John Whaley, Founder and CEO of UnifyID. She asked him about his company’s challenges and solutions.

Safety Detectives: What was your journey to cybersecurity and what do you love about it?

John Whaley: I went to MIT for undergrad where I majored in computer science and learned about how security is implemented in the real world. During my Ph.D. at Stanford, my thesis was on the static analysis of source code to automatically find bugs, security flaws, and security holes within the software.

I founded my first company out of Stanford which was in the security space, and now I’ve started a second company in the space as well.

SD: What motivated you to start UnifyID?

JW: What I found was that every time you type a key on the keyboard, it sends a network packet, the content of which was encrypted, but you could look at the timing between the packets and then, based on that, you could determine the timing of a user’s keystroke as they typed. So we built a demo of this solution for a security conference.

It turns out that if you know the timing of somebody’s keystrokes, then you can figure out with fair reliably what it is that they are typing because, as you move your fingers around a keyboard, the spacing between them and the duration of the time between keystrokes can leak the information about what you are typing.

We used Wireshark in the demo to capture a packet trace between the client and the server for some of these major products. Then we dumped that packet trace into a tool that would look at the timing between each of the packets, and then based on that, try to make a prediction about what the user was typing.

SD: What have been some challenges?

One of the challenges we had in building the demo was the fact that everyone has their own unique way of typing. And so, you could train a model that would work well for one person, but it wouldn’t necessarily work well for other people. That’s where we first got interested in noticing habits and idiosyncrasies that we could use for identity authentication. I noticed that passwords were a real challenge. Moving forward, we knew that the password alone was not going to be the way that people would be authenticated. While the password is not completely going away yet, we are starting to see its limitations and the need for additional authentication factors to provide secure digital experiences.

SD: Which industries use UnifyID and why?

JW: We have a lot of interest from the financial services industry because fraud is very costly in that area; they have a need for high security but there’s also a need for seamless user experience. The other areas are cryptocurrencies and crypto exchanges. Any type of case where there is a sharing economy where you need to authenticate not only the user, but also the worker, because the worker may not be a full-time employee of the company, and they want to make sure that the correct person is the one making a delivery or walking your dogs.

In many cases, people use our technology for streamlining physical access: for unlocking doors and cars for example, where you want security and you also want a seamless user experience.

SD: What do you feel is the worst cyberthreat today? 

JW: The biggest cyber threat continues to be the attacks that go after the end-user. We’ve reached a point now where firewalls are no longer easy targets. It is now much easier and much more lucrative to go after individuals and try to steal their identity during the authentication process by tricking them into authenticating. This way the attacker hijacks the individual’s session to take over their account and then either transfer money out or use the hijacked account as a launch point for new attacks.

When I was young, hackers were hobbyists who were hacking for fun to prove something. There was not a lot of money in it, and it was not particularly malicious. Fraud is now a cybercrime and cyberattacking is now a large industry. There is a lot of money in it. The attacks have gotten very sophisticated. Attackers will steal someone’s identity, wreck their credit, and use that to launch different types of attacks to try to extract money out of even more people.

Until now, humans have always been the weak link in security—getting tricked into either clicking through a phishing site, entering their password in the wrong place, or getting socially engineered over a phone call. WIth UnifyID’s behavioral biometrics technology based on motion and the way each one of us behaves, humans become a strong link in security just by behaving the way they usually do.

“Suddenly there is a much greater need to remotely authenticate people…”

John Whaley

SD: How important is multifactor authentication in the light of COVID-19 and the increase of employees working from home?

JW: The number of attacks has increased by almost 800% since the start of COVID-19. In the recent past, you were able to implicitly be authenticated due to the fact you were physically at the office, which takes security measures to let you into the building itself. Now, with everyone working remotely, suddenly there is a much greater need to remotely authenticate people as now a larger number of us works remotely.

One of the additional drivers for hacking is the current economic situation. In the current world environment, more and more people are out of work and lacking positive economic prospects. These conditions could drive more people to engage in hacking.

Interview originally published on Safety Detectives.

The Power of Trusted Device Registration

Welcome once again to the Power of PushAuth™ blog series! This post builds on the previous posts by providing an extension of our sample project with feature and security enhancements.

The Problem of Unchecked Device Registration

In the “Is push authentication perfect?” section of the first post in the series, we pointed out some shortcomings of relying on push authentication as a means to authenticate users. Recall the following from that section:

“There’s also the issue of trusting a device to be associated with its true user during registration, as well as not allowing attackers to register devices under the true user’s account.”

The first iteration of the open source sample project did not provide a solution to this problem. With that initial, simple implementation of PushAuth, there was virtually no confidence in the devices registered under a given username. If an attacker were able to obtain the SDK key and one or more usernames registered on the server, they could configure the app on their phone and fraudulently accept or reject push notification login attempts. This security hole allows attackers to impersonate true users and access their resources in the website, or to lock a true user out of accessing their own resources. Not great, considering this is supposed to be a method of increasing the security of the authentication flow.

Trusted Device Registration

The extension presented in this post, however, does include trusted device registration! It also provides the added feature of user registration in the website. Remember that the initial project required users to be created in the console. Only users whose username and password were inserted to the database in that manner were able to log in to the website and receive push notifications. Now users register in the website directly. Once a user signs up in the website, they configure the sample app on their phone in order to authorize future login attempts. This is where trusted device registration comes into play.

What’s different for the user?

After dictating what username and password to associate with a new user, the website displays a four-digit pairing code. The user is instructed to enter that pairing code in the sample app with their chosen username. If the values match, then the user will receive push notifications during login attempts and be able to respond accordingly. If the username and pairing code do not match, then registration of that mobile device fails. This means they will not be able to receive push notifications to confirm or deny login attempts. An attacker will not know that unique pairing code, so they are unable to impersonate a user in the app.

How does this work?

The PushAuth sample server now has a verification endpoint, /users/trust, which accepts webhook requests from UnifyID’s PushAuth service. UnifyID’s server makes an HTTP POST request to a configured target URL, which is set in the project’s dashboard. The request body contains the username and the pairing code. The /users/trust endpoint looks up the given username and determines if the provided pairing code matches the server-generated code displayed during that user’s registration. If the request returns 200, then the user is considered verified and the device trusted. However, if the request returns anything else, the user is not considered verified and device registration fails.

The webhook target URL must use the https scheme and requests use the ‘Basic’ HTTP Authentication scheme, which can be used to validate requests. Further, the four-digit alphanumeric code generated by the server is single-use. These details collectively make it harder for an attacker to impersonate users. An attacker does not have access to the pairing code during initial sign up and they also cannot reuse the pairing code, thus ensuring that devices registered with users are trusted to belong to the true user.

Note: The way we chose to implement trusted device registration with a pairing code is by no means the only method of implementing trusted registration via webhook.

What are the limitations?

If you remember from our first post in the series, there will seemingly always be security holes in a login flow. This enhancement is no exception. For one, the pairing code is only displayed during user registration. What if the user accidentally closes out the tab and then has no way to pair their device? Or what if something happens with the device or app on their device and they have no way to reconfigure an app to be tied to their user? There’s also the concern of in-person over-the-shoulder access to the pairing code, where an attacker could see the four digits and enter the code on their phone before the true user is able to do so. However, just like before, there are solutions to those problems as well; they just aren’t included here. The extension we provide is certainly a big security improvement and patches one of the larger holes that existed in the initial sample project.

Developer Changes

Now that we’ve gone over the concept of trusted device registration and the value it adds to the PushAuth sample project, we’ll walk you through the actual changes from a developer’s perspective. The new repositories can be found here:

We have videos on the UnifyID YouTube channel to walk you through the full end-to-end flow of adding PushAuth to a website login flow with trusted registration.

Trusted Registration Webhook

First and foremost, you’ll need to enable trusted registration for your project in the dashboard. Reference the Setting up Trusted Registration section of our documentation to do so.

The server needs to be publicly available for the target URL to be used. If you want to follow along the tutorial without hosting the web server, we suggest using ngrok to expose the sample rails server running locally on your machine to the internet. The steps shown in this post will do that.

$ ngrok http 3000

Session Status                online
Account                       morganfrisby (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface       
Forwarding                    http://9c6b07ea6861.ngrok.io -> http://localhost:3000
Forwarding                    https://9c6b07ea6861.ngrok.io -> http://localhost:3000

The target URL is the https URL forwarding to your local server, with /users/trust appended. For example:

Once you add the target URL, trusted registration is enabled for your project. The username and password displayed are used in HTTP Basic Authentication. The username value is set to unifyid, and the password is an auto-generated random string. This value can be rotated, should it become compromised.

Now that the trusted registration webhook is set up in the dashboard, you’re ready to move on to setting up the server.

Server Setup

Other than that, server setup more or less follows the instructions from the first tutorial. Since users are now registered on the website, there is no need to create users in the console during server setup. The other change is the addition of basic_username and basic_password to the credentials file, which are the values found in your project dashboard under the Trusted Registration Webhook section, shown above.

The streamlined steps to spin up the server are now:

$ git clone https://github.com/UnifyID/pushauth-sample-server-reg.git
$ cd pushauth-sample-server-reg
$ bundle install
$ yarn install --check-files
$ bundle exec rails db:migrate

$ EDITOR=vim rails credentials:edit
  server_api_key: <your_key_goes_here>
  basic_username: <basic auth username for /users/trust webhook endpoint>
  basic_password: <basic auth password for /users/trust webhook endpoint>

$ bundle exec rails server

Feel free to reference the detailed tutorial in our previous post for more context around those commands, or the technical details blog post to understand how the server was built. Our next post in the series will provide the technical details of the trusted registration extension of the sample server.

You can see the new website screenshots here (notice the pairing code in the third screenshot):

Sample App Setup

Nothing changes from the developer’s perspective for the iOS and Android sample apps; simply clone the updated repositories and follow the same instructions from earlier in the blog series. Those two posts can be found here:

The iOS sample app screens will look like:

The Android sample app screens now look like:

That’s it! Thanks for following along with the Power of PushAuth™ blog series. The next, and final, post will include the technical details of adding trusted registration to the sample project. Until then, feel free to reach out to us with questions or comments at https://unify.id/contact-us/.

Building a Web Application with PushAuth™

Welcome back to the Power of PushAuth™ blog series! This is the fifth post of the Power of PushAuth™ blog series. The first post of the series was a comprehensive guide to push authentication. The subsequent three posts comprised an end-to-end sample implementation of PushAuth™ in a simple user login flow:

  1. Web Server tutorial
  2. iOS Mobile App tutorial
  3. Android Mobile App tutorial

In this post, we will create a sample website from scratch using Rails, and will integrate PushAuth™ APIs into the user login flow. Along the way, we will also provide technical details on how the website interacts with PushAuth™ APIs, which can help readers incorporate PushAuth™ into any existing website. The end result of this tutorial will be similar to the sample web server we deployed in Web Server tutorial.


To follow this tutorial, you will need:

This tutorial assumes a basic familiarity with the Rails framework.

Step 1: Make basic Rails app with simple session-based authentication

First, we will create website with a simple username/password authentication, without incorporating UnifyID PushAuth™. Step 2 will integrate PushAuth™ into the website we create in Step 1.

Project Initialization

$ rails new push_auth_demo
$ cd push_auth_demo

Let’s add the bcrypt gem to our Gemfile by uncommenting this line in Gemfile:

gem 'bcrypt', '~> 3.1.7'

Now, we will run bundle install to update the Gemfile.lock.

Next, we can add the User model to our database; each User will have a username and a password hash.

$ rails generate model user username:uniq password:digest 
$ rails db:migrate

Now, we can generate the controller for handling sessions.

$ rails generate controller sessions new create destroy

Controller Logic

The basic idea of session-based authentication is pretty simple:

  • When a user logs in, session[:user_id] is set to be the unique index of the corresponding User in the database.
  • When no one is logged in, session[:user_id] should be unset.

We’ll start with writing the Application controller, where we have a couple of simple page actions:

  • GET / (application#home) renders a page that shows links to other pages and actions
  • GET /restricted (application#restricted) renders a page that is only accessible when logged in.

A few helper functions will live here as well:

  • current_user should either return a User object, or nil if there’s no one logged in.
  • logged_in? should return whether someone is logged in
  • authorized is an action that is called before loading pages that require the a user to be logged in.
# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :authorized
  helper_method :current_user
  helper_method :logged_in?

  skip_before_action :authorized, only: [:home]

  def current_user
    User.find_by(id: session[:user_id])

  def logged_in?

  def authorized
    unless logged_in?
      redirect_to login_path, alert: "You must be logged in to perform that action."

Now, we’ll handle users logging in or out through the Sessions controller.

  • GET /login (sessions#new) action displays a login page unless the user is already logged in
  • POST /login (sessions#create) will authenticate the user and set the session.
  • DELETE /logout (sessions#destroy) action will clear the user’s session cookie.
# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  skip_before_action :authorized, except: [:destroy]

  def new
    redirect_to root_path if logged_in?

  def create
    @user = User.find_by("lower(username) = ?", params[:username].downcase)
    if @user && @user.authenticate(params[:password])
      session[:user_id] = @user.id
      redirect_to root_path, notice: "Successfully logged in!"
      redirect_to login_path, alert: "Sorry, that didn't work."

  def destroy
    session[:user_id] = nil
    redirect_to root_path, notice: "Successfully logged out."

We also need to add routes for these actions.

# config/routes.rb

Rails.application.routes.draw do
  root "application#home"

  get "restricted", to: "application#restricted"

  get "login", to: "sessions#new"

  post "login", to: "sessions#create"

  delete "logout", to: "sessions#destroy"


home page where you can click on the link to log in/out:

<!-- app/views/application/home.html.erb -->

<h1> Welcome! </h1>

<% if logged_in? %>
  Welcome, <%= current_user.username %>. <br />
  <%= link_to "Log out", logout_path, method: :delete %> <br />
  Click <%= link_to "here", restricted_path %> to see a super secret page!
<% else %>
  Please <%= link_to "log in", login_path %>.
<% end %>

restricted page to test access control:

<!-- app/views/application/restricted.html.erb -->

Shhh, this page is a secret!

sessions#new renders simple login form:

<!-- app/views/sessions/new.html.erb -->


<%= form_tag "/login", {class: "form-signin"} do %>
  <%= label_tag :username, nil, class: "sr-only" %>
  <%= text_field_tag :username, nil, class: "form-control", placeholder: "Username", required: true, autofocus: true %>

  <%= label_tag :password, nil, class: "sr-only" %>
  <%= password_field_tag :password, nil, class: "form-control", placeholder: "Password", required: true%>

  <%= submit_tag "Log in", {class: ["btn", "btn-lg", "btn-primary", "btn-block"]} %>
<% end %>

Lastly, we’ll modify the default template to include a navigation bar at the top and flash messages for notice and alert from controllers:

<!-- Replace the contents of the <body> tag in app/views/layouts/application.html.erb with the following -->

    <nav class="navbar navbar-dark bg-dark">
      <a class="navbar-brand" href="/">
        <span class="logo d-inline-block align-top"></span>
        UnifyID PushAuth Sample
    <% if flash[:notice] %>
      <div class="alert alert-primary alert-dismissible fade show" role="alert">
        <%= flash[:notice] %>
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
          <span aria-hidden="true">×</span>
    <% end %>
    <% if flash[:alert] %>
      <div class="alert alert-danger alert-dismissible fade show" role="alert">
        <%= flash[:alert] %>
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
          <span aria-hidden="true">×</span>
    <% end %>

    <main role="main" class="container">
      <div class="main-content">
        <%= yield %>


We can add styling to our website by simply adding bootstrap. The views we created above already use class names that are recognized by bootstrap.

First, add bootstrap and some of its dependencies.

$ yarn add bootstrap jquery popper.js

Then, add the following to the end of app/javascript/packs/application.js:

import 'bootstrap'
import 'stylesheets/application.scss'

Next, make a file called app/javascript/stylesheets/application.scss and add this:

@import "~bootstrap/scss/bootstrap";

Optionally, you may add your own custom CSS files as well. See an example in our sample code.

At this point, you should be able to run rails server and navigate to http://localhost:3000 to interact with the basic authentication server! You can create sample users as follows:

$ bundle exec rails console
> User.create(:username => "<your_username>", :password => "<your_password>").save
> exit

Step 2 – Integrate with UnifyID PushAuth™ APIs

Now that we have a website with a simple username/password authentication, let’s incorporate UnifyID PushAuth™ APIs to further enhance security.

Interface to PushAuth™ APIs

First, let’s tell Rails about your UnifyID API key.

Run rails credentials:edit and add the following

  server_api_key: <Your UnifyID API Key created from dashboard>

Next, add the following to config/application.rb, after the config.load_defaults line:

config.x.pushauth.base_uri = "https://api.unify.id"

Let’s also add the httparty gem to easily make HTTP/S requests. To do this, add the following to your Gemfile and run bundle install:

gem 'httparty', '~> 0.18.0'

Now, we will make a file called app/services/push_auth.rb which contains the interface for our Rails app to interact with the PushAuth™ APIs:

  • create_session method calls POST /v1/push/sessions to initiate PushAuth™ session (API doc).
  • get_session_status method calls GET /v1/push/sessions/{id} to retrieve the status of PushAuth™ session (API doc).
# app/services/push_auth.rb

class PushAuth
  include HTTParty
  base_uri Rails.configuration.x.pushauth.base_uri

  @@options = {
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": Rails.application.credentials.unifyid[:server_api_key]

  def self.create_session(user_id, notification_title, notification_body)
    body = {
      "user" => user_id,
      "notification" => {
        "title" => notification_title,
        "body" => notification_body
    post("/v1/push/sessions", @@options.merge({body: body.to_json}))

  def self.get_session_status(api_id)
    get("/v1/push/sessions/#{api_id}", @@options)

Controller Logic Modification

Now, we will modify the login flow to incorporate PushAuth™ as the second factor authentication. The new login flow will consist of the following:

  1. The client submits the username and password via a POST request to /login (sessions#create)
  2. The controller validates that the user exists and the password matches. If not, it displays an error message.
  3. Upon successful username/password authentication, the controller creates a PushAuth™ session and redirects to GET /mfa (sessions#init_mfa)
  4. The Javascript in /mfa page repeatedly queries GET /mfa/check (sessions#check_mfa), which checks the PushAuth™ session status until the session status is no longer pending.
  5. Upon receiving a non-pending session status, the client submits a request to PATCH /mfa/finalize (sessions#finalize_mfa) that completes the login process.

First, let’s replace the create action in app/controllers/sessions_controller.rb:

  def create
    @user = User.find_by("lower(username) = ?", params[:username].downcase)
    if @user && @user.authenticate(params[:password])
      session[:pre_mfa_user_id] = @user.id

      pushauth_title = "Authenticate with #{Rails.application.class.module_parent.to_s}?"
      pushauth_body = "Login request from #{request.remote_ip}"

      response = PushAuth.create_session(@user.username, pushauth_title, pushauth_body)

      session[:pushauth_id] = response["id"]

      redirect_to mfa_path
      redirect_to login_path, alert: "Sorry, that didn't work."

Next, let’s also add check_mfa and finalize_mfa actions in this controller:

  def check_mfa
    status = PushAuth.get_session_status(session[:pushauth_id])["status"]

    render plain: status

  def finalize_mfa
    case PushAuth.get_session_status(session[:pushauth_id])["status"]
    when "accepted"
      session[:user_id] = session[:pre_mfa_user_id]
      session[:pushauth_id] = nil
      session[:pre_mfa_user_id] = nil
      flash.notice = "Successfully logged in!"
    when "rejected"
      session[:pre_mfa_user_id] = nil
      flash.alert = "Your request was denied."

We also want to make sure that only users who completed the password authentication are able to access actions for the PushAuth™ authentication. Thus, within sessions_controller we will add:

# app/controllers/sessions_controller.rb

# Add this just under the skip_before_action line
  before_action :semi_authorized!, only: [:init_mfa, :check_mfa, :finalize_mfa]

# And add this after action methods
  def semi_authorized
    session[:pre_mfa_user_id] && session[:pushauth_id]

  def unauthorized
    redirect_to login_path, alert: "You are not authorized to view this page."

  def semi_authorized!
    unauthorized unless semi_authorized


Now, we need a page that uses AJAX to determine whether the PushAuth™ request has been completed.

First, let’s add a line in our application template that allows us to add content inside the <head> tag.
Add this to app/views/layouts/application.html.erb, right before the </head> tag:

<%= yield :head %>

Next, let’s add the Javascript code we want to run on the init_mfa page:

// app/javascript/packs/init_mfa.js

import Rails from "@rails/ujs";
let check_status = window.setInterval(function() {
    type: "GET",
    url: "/mfa/check",
    success: function(r) {
      if (r !== "sent") {
          type: "PATCH",
          url: "/mfa/finalize",
          success: function() {
            window.location.href = "/";
          error: function() {
            console.log("Promoting PushAuth status failed.");
    error: function() {
      console.log("Checking for PushAuth status failed.");
}, 2000);

This will poll /mfa/check every 2 seconds, until the Rails app reports that the PushAuth™ request has been accepted, rejected, or expired. At this point, the browser will ask the Rails app to complete the login process by submitting a /mfa/finalize request.

Now, let’s add a view file for init_mfa that includes the Javascript above.

<!-- app/views/sessions/init_mfa.html.erb -->

<% content_for :head do %>
  <%= javascript_pack_tag 'init_mfa' %>
<% end %>

<div class="spinner-border" role="status" ></div>

Waiting for a response to the push notification...

Finally, we will add new mfa routes.

# Add these to config/routes.rb

  get "mfa", to: "sessions#init_mfa"

  get "mfa/check", to: "sessions#check_mfa"

  patch "mfa/finalize", to: "sessions#finalize_mfa"

Congratulations! We have now integrated UnifyID’s PushAuth™. The final result should function just like the pushauth-sample-server project, which we introduced in our How to Implement PushAuth™: Web Server post. Please reach out to us if you have any questions, comments or suggestions, and feel free to share this post.