MIRACL 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: {
clientId: "<YOUR_CLIENT_ID>",
redirectUri: "https://example.com/login",
},
});
The projectId
and clientId
you can obtain by following the
Get started guide. We recommend setting up a separate
redirectUri
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 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
: stringgetItem(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 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 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 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 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 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 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: {
clientId: "<YOUR_CLIENT_ID>",
redirectUri: "https://example.com/login",
},
});
// 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 client in the Home screen:
import React, { Component } from "react";
export default class Home extends Component {
componentDidMount() {
// Get the injected MIRACL 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 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) {
const params = this.parseParams(initialURL);
this.props.miracl.confirm(params, (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 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 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);
// });
});
}
}
In order to verify the code in your back-end and get the identity of the 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 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 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 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 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 user’s PIN
authenticates the session and log the 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);
});
}
}