React Native Integration

MIRACL Trust offers a JS client library which implements the client-side operations of the M-PIN authentication protocol.

# Installation

npm install --save @miracl/client-js

# Configuration and Setup

Before using the library you need to first create a configured instance of the client:

import Client from "@miracl/client-js";

new Client({
  projectId: "<YOUR_PROJECT_ID>",
  seed: hexSeed,
  userStorage: storage,
  oidc: {
    client_id: "<YOUR_CLIENT_ID>",
    redirect_uri: "<YOUR_REDIRECT_URL>",
    response_type: "code",
    scope: "openid",
  },
});

The projectId and client_id you can obtain by following the Get started guide. We recommend setting up a separate redirect_uri for the application in the MIRACL Trust Console, although this is not used for OIDC redirection.

# The Seed for the Random Number Generator

The seed is used for initializing the random number generator necessary for the security of the authentication protocol. Let us use the react-native-securerandom library in order to generate the seed:

import { generateSecureRandom } from "react-native-securerandom";

export async function generateHexSeed() {
  const randomBytes = await generateSecureRandom(8);

  let hexSeed = "";
  randomBytes.forEach((b) => {
    hexSeed += b.toString(16);
  });

  return hexSeed;
}

# The Storage

The MIRACL Trust client needs persistent storage for the identity data stored during registration and used for authentication. The client does not support asynchronous storage and expects an object, which has the following methods implemented:

  • setItem(name, value)
    name: string
    value: string
  • getItem(name)
    name: string
    returns string

In order to implement both this interface and avoid the asynchronous nature of most persistent storage mechanisms available in React Native, we can create a wrapping class. The class keeps an in-memory cache of the data in storage and maintain consistency by synchronizing with the async storage. Here is an example using @react-native-community/async-storage:

import AsyncStorage from "@react-native-community/async-storage";

export default class UserStorage {
  storage = {};
  initialized = false;

  // The instance needs to be initialised asynchronously
  // before passing it to the MIRACL client
  async init() {
    if (this.initialized) {
      return this;
    }

    this.storage.mfa = await AsyncStorage.getItem("mfa");
    this.initialized = true;

    return this;
  }

  setItem(name, value) {
    this.storage[name] = value;
    return AsyncStorage.setItem(name, value);
  }

  getItem(name) {
    return this.storage[name] ? this.storage[name] : null;
  }
}

Although MIRACL Trust authentication does not rely on the security of the storage, we recommend using a secure storage alternative. You can read more in the Secure Storage section of the React Native official documentation.

# Creating Only One Instance

As we don’t want to create multiple unnecessary instances of the MIRACL Trust client, we can use an instance manager pattern, which creates and configures just one instance of the client and then pass that instance to any requester.

import Client from "@miracl/client-js";

const Manager = {
  instance: null,

  configure: function (config) {
    this.instance = new Client(config);
  },

  getInstance: function () {
    if (!this.instance) {
      throw new Error("Not configured. First call the configure method.");
    }

    return this.instance;
  },
};

export default Manager;

This pattern works due to the ES6 module system. This module (where the object is defined) is loaded once, even if it’s imported in multiple places. This loosely ensures that there is only one instance of the manager and so only one instance of the MIRACL Trust client. This also gives the benefit of configuring the client in just one place and then reusing it where needed.

# Higher-Order Component

In order to use the MIRACL Trust client in your components you can utilise the higher-order component (HOC) technique. Here is an example of a HOC implementation:

import React, { Component } from "react";

export function withMiracl(WrappedComponent) {
  return class extends Component {
    render() {
      return (
        <WrappedComponent miracl={Manager.getInstance()} {...this.props} />
      );
    }
  };
}

Although you can use the manager directly in any component that needs an instance to the MIRACL Trust client, the HOC technique has the benefit of configuring and injecting the client in just one place. You can see an example in the following section.

# Tie it All Together

With all of the setup we’ve done in the previous sections our App.js can now tie everything together:

import React, { Component } from "react";
import UserStorage from "./miracl/UserStorage";
import Miracl, { withMiracl, generateHexSeed } from "./miracl/Miracl";

import HomeScreen from "./screens/Home";
import LoadingScreen from "./screens/Loading";

// Create the HOC that has the
// MIRACL Trust client instance as a property
const HomeScreenWithMiracl = withMiracl(HomeScreen);

export default class App extends Component {
  state = {
    ready: false,
  };

  // We can use the async support for the lifecycle methods
  // to ensure that all of the setup is complete before
  // attempting any operations
  async componentDidMount() {
    const storage = await new UserStorage().init();
    const hexSeed = await generateHexSeed();

    Miracl.configure({
      projectId: "<YOUR_PROJECT_ID>",
      seed: hexSeed,
      userStorage: storage,
      oidc: {
        client_id: "<YOUR_CLIENT_ID>",
        redirect_uri: "<YOUR_REDIRECT_URL>",
        response_type: "code",
        scope: "openid",
      },
    });

    // Change the state after everything is configured
    this.setState({ ready: true });
  }

  render() {
    // Don't render the home screen until setup is complete
    if (this.state.ready) {
      return <HomeScreenWithMiracl />;
    } else {
      return <LoadingScreen />;
    }
  }
}

Now we can use the MIRACL Trust client in the Home screen:

import React, { Component } from "react";

export default class Home extends Component {
  componentDidMount() {
    // Get the injected MIRACL Trust client instance
    const miracl = this.props.miracl;

    // Print a list of registered users
    console.log(miracl.users.list());
  }
}

# Identity Verification

In order to register an identity, you must first obtain a registration code from MIRACL Trust. You can do this by using either the default MIRACL Trust verification process or using Custom User Verification.

# Custom User Verification

If you have custom verification configured for your project, you can follow the Custom User Verification guide to obtain the registration code. Then you can simply call the register method from a component as described in the Registration section.

# Default Verification

If you are using the default (email) verification, you can start the process by calling the verify method:

import React, { Component } from "react";

export default class Verification extends Component {
  verify(emailAddress) {
    this.props.miracl.sendVerificationEmail(emailAddress, (err, data) => {
      if (err) {
        return console.error(JSON.stringify(err));
      }

      console.log(data);
    });
  }
}

This sends an email to the provided address with a verification link. The verification link needs to be handled as a deep link by your application. You can follow the React Native linking documentation to see how to set up the handling of that link. When the end user opens the email link and the app is launched, you need to call the confirm method in order to finish the verification process and obtain a registration code:

import React, {Component} from 'react';

export default class Confirmation extends Component {
  // Parse the deep link URI and return the query parameters
  parseParams(URI) {
    const regex = /[?&]([^=#]+)=([^&#]*)/g;

    let params = {},

    var match;
    while (match = regex.exec(url)) {
      params[match[1]] = match[2];
    }

    return params;
  }

  confirm(initialURL) {
    this.props.miracl.getActivationToken(initialURL, (err, data) => {
      if (err) {
        return console.error(JSON.stringify(err));
      }

      console.log(data);
    });
  }
}

# Registration

After you have the registration code, you only need to call the register method in order to create and store the authentication identity on the device.

import React, { Component } from "react";

export default class Registration extends Component {
  register(userId, registrationCode) {
    this.props.miracl.register(
      userId,
      registrationCode,
      (passPin) => {
        // Here you should prompt the user to enter a PIN
        passPin("1111");
      },
      (err, data) => {
        if (err) {
          return console.error(JSON.stringify(err));
        }

        console.log(data);
      },
    );
  }
}

# Authentication

# Authenticating Inside the App

If you want your end users to authenticate before using the functionality in the application, you can use the generateAuthCode method. In the success callback you get an object containing an auth code. This auth code can be exchanged for a token verifying the identity of the end user.

import React, { Component } from "react";

export default class Authentication extends Component {
  authenticate(qrUrl, userId, pin) {
    this.props.miracl.generateAuthCode(userId, pin, (err, data) => {
      if (err) {
        console.log(JSON.stringify(err));
      }

      console.log(data);

      // Send data.code to your back-end
      // fetch('https://mywebsite.com/endpoint/', {
      //   method: 'POST',
      //   body: JSON.stringify({
      //     code: data.code
      //   })
      // })
      // .then((response) => response.json())
      // .then((json) => {
      //   console.log(json);
      // });
    });
  }
}

To verify the code in your back-end and get the identity of the end user, you need to make the following request to api.mpin.io:

curl \
  --request POST \
  --data "grant_type=authorization_code&client_id=${YOUR_CLIENT_ID}&client_secret=${YOUR_CLIENT_SECRET}&code=${CODE}&redirect_uri=${YOUR_REDIRECT_URL}" /
  https://api.mpin.io/oidc/token

In the response you get an id_token, which is a JWS that can be verified using the keys published on https://api.mpin.io/oidc/certs. You can then use any mechanism you want in order to establish the authenticated communicating between your back-end and your mobile application. An example would be to issue a session token and send it to the application in the response for the code exchange.

# Using the App as an Authenticator

The application can also serve as an authenticator, which means that after a successful authentication the end user is logged in on a different device. In order to link the authentication session between devices we need an identifier of the session and a method to transfer it. Since the authentication session is generated for the device that the end user is logged in to, it needs to travel to the application that we are building. MIRACL Trust offers several transfer mechanisms, but the most easy one to set up is the QR code, so that’s what we are going to be using. The end user needs to scan the QR code displayed on the MIRACL Trust authentication page in their browser with our application. To scan the QR code and decode that data contained in it, we can use a package like react-native-qrcode-scanner. The QR code contains an URL as a fallback if the end user scans it with a different application and contained in the URL fragment is an identifier used for access to the session (the actual session ID is not exposed to prevent session hijacking attacks). We call this identifier access ID. We extract the access ID from the URL and pass it to the client library using the setAccessId method. After that simply calling authenticate with the User ID and the end user’s PIN authenticates the session and logs the end user in their browser where the QR code was displayed.

import React, { Component } from "react";

export default class Authentication extends Component {
  authenticateWithQR(qrUrl, userId, pin) {
    // Extract the access ID from the QR URL
    const accessId = qrUrl.split("#").pop();

    // Set the extracted ID, so that we can authenticate the same session
    this.props.miracl.setAccessId(accessId);

    // And then just call the authenticate method
    this.props.miracl.authenticate(userId, pin, (err, data) => {
      if (err) {
        console.log(JSON.stringify(err));
      }

      console.log(data);
    });
  }
}