Firebase integration

MIRACL Trust does not replace Firebase Authentication. Instead it integrates with it by using Firebase Functions to produce custom signed tokens when a user successfully authenticates using MIRACL Trust. Your app receives this token and uses it to authenticate with Firebase. You can read more about how to generate and use custom tokens in the Firebase documentation

The Authentication Flow

  1. Your application redirects the user to the MIRACL Trust authentication page
  2. The user authenticates using their PIN
  3. The user is redirected back to your application with an OIDC authorization code
  4. Your application calls the Firebase function responsible for exchanging the authorization code for a Firebase custom auth token
  5. The Firebase function exchanges the OIDC authorization code for ID and access tokens
  6. The Firebase function verifies the ID token and extracts the email from the claims
  7. The Firebase function finds the user with the same email
  8. The Firebase function generates and returns the Firebase custom auth token
  9. Your application calls the Firebase Authentication signInWithCustomToken method
  10. The user is now properly authenticated against Firebase

The Firebase Cloud Function

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const util = require('util');

// Initialize Firebase admin SDK
const serviceAccount = require('./path/to/firebase-adminsdk-service-account.json');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

// Configure utilities for JWT verification
const jwtVerify = util.promisify(jwt.verify);

const client = jwksClient({
  jwksUri: 'https://api.mpin.io/oidc/certs'
});

const getKey = (header, callback) => {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) {
      return callback(err, null);
    }

    const signingKey = key.publicKey || key.rsaPublicKey;

    return callback(null, signingKey);
  });
};

// MIRACL Trust OIDC configuration
const clientID = 'xxxxxxxxxxxxx';
const clientSecret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const redirectURI = 'http://localhost:8080/auth';

exports.getAuthToken = functions.https.onCall(async (data, context) => {
  // Exchange the code for OIDC tokens from MIRACL Trust
  const miracl = await axios.post('https://api.mpin.io/oidc/token', 'grant_type=authorization_code&client_id='+clientID+'&client_secret='+clientSecret+'&redirect_uri='+redirectURI+'&code='+data.code)

  // Verify the received ID token
  const payload = await jwtVerify(miracl.data.id_token, getKey, { algorithms: ['RS256'] });

  // Find the user with the specified email
  const user = await admin.auth().getUserByEmail(payload.sub);

  // Generate a custom token for Firebase authentication
  const customToken = await admin.auth().createCustomToken(user.uid);

  return {
    token: customToken
  };
});

Using the Custom Token

// Get a reference to the callable function
const getAuthToken = firebase.functions().httpsCallable('getAuthToken');

// Exchange the MIRACL Trust authorization code for a Firebase token
getAuthToken({ code: getURLQueryParam('code') })
  .then((result) => {
    // Get the token from the Cloud Function result
    const token = result.data.token;

    // Sign in with the returned token
    firebase.auth().signInWithCustomToken(token)
      .then((userCredential) => {
        const user = userCredential.user;
        console.info('Logged in as', user.email);
      })
      .catch((error) => {
        console.error(error);
      });
  })
  .catch((error) => {
    console.error(error);
  });