How to Connect a Two-Factor Authentication (2FA) API to a Smart Contract With Chainlink

Digital Bridge, the Chainlink Judges’ Pick Award from the Unitize Hackathon, used a Chainlink External Adapter to connect a 2FA API to a Chainlink oracle for decentralized verification of a 2FA PIN’s validity. Their 2FA smart contract is a great example of how Chainlink’s customizable and secure oracle infrastructure can be used to connect off-chain APIs to smart contracts, enabling new, highly configurable on-chain security workflows.

In this post, the Digital Bridge team, Javier Salomon, Alejandro Pronotti, and Mateo Hepp, explain how to integrate a Chainlink 2FA adapter into an Ethereum dApp so it can be used across a number of use cases, from securing DeFi funds to KYC verification.


By Alejandro Pronotti, Javier Salomon, and Mateo Hepp

Chainlink’s External Adapter feature makes it easy to connect smart contracts to any API, enabling a variety of use cases for smart contracts to trigger off-chain events and bring tamper-proof digital agreements to external systems.

For the Digital Bridge technical integration, we developed a Chainlink External Adapter to read an off-chain, high-availability 2FA API authentication service, and we also configured a custom Digital Bridge Chainlink oracle node that relays the secret codes needed to confirm 2FA. The oracle can implement an External Adapter using Amazon AWS Lambda, Google Cloud Platform functions, or Docker.

In order to authenticate the user holding the 2FA secret code, a user submits a transaction on-chain containing their customer ID and the hash of their temporary one-time password generated by their authenticator app. A specified Chainlink node scanning the blockchain then picks up on this transaction and queries an off-chain server (via API) to validate the 2FA code’s authenticity. Once the Chainlink node receives a response, it delivers this Boolean value on-chain, which, if authorized TRUE, then triggers the smart contract to grant authorization to the original user.

This implementation avoids man-in-the-middle attacks by using a hash of the PIN and an External Adapter to compare the PIN to the off-chain authenticator.

In this technical article, we’ll go through:

  • The 2FA API usage
  • Installing and running the 2FA External Adapter
  • How to attach this External Adapter to a Chainlink node via a Bridge and Job Spec
  • How to write a smart contract to use the 2FA External Adapter via a Chainlink node to verify the 2FA  PIN validity

2FA API Usage

To call a 2FA API, you can use your own customized API or a third-party API with some minimal changes to the adapter. To facilitate testing and development, we provide a demo API with sample users, which is accessible with demo API keys.

The 2FA Demo API allows verification of the 2FA PIN validity with the following `customerid` variable values and API_KEYs.

Demo sample users

  • customerid = 'alice' , secretcode = 'VPPRAX5ZS3EAT3ID'
  • customerid = 'bob' , secretcode = 'O73Y5FPODOZXHJ4G'
  • customerid = 'joe' , secretcode = '6VG5WWIDWHLR3SYE'

Demo Authorized API Keys

  • yyTHPaT2n27n3bva9mhX
  • sDVRwdc4NXaKP4PZ7CuW
  • viaMmedQyaWb2DQeF7dL

Input Params

The structure for the JSON input is as follows.

 {
  "id": "f82dfad608254bc7a36364f317e47a4d", 
  "data": {
      "customerid": "alice", 
      "hashedpin": "32532605273527"}
}

In this example, the jobSpec is f82dfad608254bc7a36364f317e47a4d. Actions may be any of the following: customerid or hashedpin.

To calculate hashedpin execute the following commands:

In Python (default, Jul 28 2020, 12:59:40)

>>> import datetime
>>> import sha3
>>> PIN = “534204”
>>> int(sha3.keccak_256(PIN.encode('utf-8')).hexdigest()[:12],16)
32532605273527

hashedpin is: 32532605273527

Here is another method to calculate hashedpin using JavaScript with a web3 module installed.

JavaScript with web3 module installed

user@ubuntu-machine:~/nodejs-web3-project$ node 
Welcome to Node.js v12.20.0.
Type ".help" for more information.
> const Web3 = require('web3');
> var PIN = '534204';
> parseInt(Web3.utils.sha3(PIN).slice(2,14),16);
32532605273527

The next step is to make a REST API call.

curl -H "authorization:Apikey yyTHPaT2n27n3bva9mhX" "https://us-central1-digitalbridge.cloudfunctions.net/TwoFA-chekpin-api?customerid=alice&hashedpin=42646170711749"

Output Example

{"result": true }

You will receive true or false value indicating whether the PIN is valid or not.

Installing and Running the 2FA External Adapter

We created an External Adapter to connect Chainlink nodes to a 2FA API to tap a Chainlink oracle for an on-chain, peer-to-peer verification of 2FA PIN validity.

This External Adapter has now been listed on the Chainlink Market and is available for other developers to use, modify, or extend.

Once you’ve downloaded the code from GitHub for the External Adapter, you can install and run it as follows.

Install Locally

Requirements

  • yarn

Install dependencies:

yarn

Run

node -e 'require("./index.js").server()'

Once you’ve installed, you can add the External Adapter to your Chainlink node and then create a Job Specification that uses it.

First, create the Bridge and the Job in the Chainlink node using the following data:

Bridge (in this case we are using GCP cloudfunctions)

Name: 2fa-maticmumbai
URL: https://us-central1-digitalbridge.cloudfunctions.net/twofa-adapter

Jobs Spec

{
  "initiators": [
    {
      "type": "runlog",
      "params": {
        "address": "0xa244b30a48559d16078bb151f342d3f12219142f"
      }
    }
  ],
  "tasks": [
    {
      "type": "2fa-maticmumbai",
      "confirmations": null,
      "params": {
      }
    },
    {
      "type": "ethbool",
      "confirmations": null,
      "params": {
      }
    },
    {
      "type": "ethtx",
      "confirmations": null,
      "params": {
      }
    }
  ],
  "startAt": null,
  "endAt": null
}

In the job spec, initiator is what triggers, or initiates, the job. In this case, the initiator is set to RunLog, which means the node will trigger this job to run when an OracleRequest event is emitted by the Oracle contract at 0xa244b30a48559d16078bb151f342d3f12219142f. This is the Oracle contract which we have deployed earlier and is unique to whichever Chainlink node you are making a request to.

Then our request will get processed by the Adapters in the sequential order that they are defined:

  1. Create a bridge type request. This bridge will be given from the client smart contract customerid and hashedpin parameters, then execute a request to an external API. The response is pipelined to the next task.
  2. Parse the result to JSON and get the value defined in a path given from the client contract as a request parameter.
  3. Convert the string type result value to an Ethereum Boolean type value.
  4. Fire the result as a transaction.

If you want to test the adapter, you can use this curl command, but you will need to replace jobid and Oracle address with the values obtained when you deployed the oracle smart contract and created the job.

Parameters for Smart Contract

Jobid: f82dfad608254bc7a36364f317e47a4d
Oracle Address: 0xA244B30a48559d16078BB151f342d3f12219142F

Command to Test the Adapter

curl -X POST  -H 'Content-Type: application/json' -d '{"id": "f82dfad608254bc7a36364f317e47a4d", "data": {"customerid": "alice", "hashedpin": "74856005982787"}}' "https://us-central1-digitalbridge.cloudfunctions.net/twofa-adapter"

Response

{
  "jobRunID":"f82dfad608254bc7a36364f317e47a4d",
  "data":{"result":true},
  "result":true,
  "statusCode":200
}

Verify the 2FA PIN Validity With a Smart Contract

Now we’re running an External Adapter, we’ve added it to a Chainlink node job specification, and we’ve added a bridge. Lastly, we will need a smart contract client that consumes this data.

The first step is to create a new API Consumer Contract, setting all the required parameters.

You should create two functions in your contract: requestGAPINCheck and fulfillGAPINCheck, as per the examples below. Call the requestGAPINCheck function to interact with the 2FA API service.

The requestGAPINCheck function takes in the customer ID and hashed PIN as parameters. This should be the ID of the first Job Specification mentioned earlier in the 2FA External Adapter section. We set the LINK payment amount to 0.1 LINK. Here is our Solidity example making an HTTP POST request through our Chainlink oracle.

function requestGAPINCheck(string memory _customerId, int256 _hashedpin)
    public
  {
    Chainlink.Request memory req = buildChainlinkRequest(JOBID, address(this), this.fulfillGAPINCheck.selector);

    req.add("customerid", _customerId);
    req.addInt("hashedpin", _hashedpin);
    req.add("path","result");

    sendChainlinkRequestTo(ORACLE_ADDRESS, req, 0.1 * 1 ether);
  }

JOBID is the first parameter of our buildChainlinkRequest. The second parameter is the address of the contract to return the data, also known as the callbackaddress. The last parameter is the function that will process the data once collected, aka “the callback function signature”. We want to return the data to this contract, so we put in address(this), and our function that will be processing the data will be fulfilled.

If the PIN is valid, the contract will return a success message and a JSON object containing a Boolean value:

{
    "result": true
}

This response data will be returned to the requestGAPINCheck function, as specified in the fields of the Chainlink.Request, where we can manually add the action required:

function fulfillGAPINCheck(bytes32 _requestId, bool _allowed)
    public
    recordChainlinkFulfillment(_requestId)
  {
    currentPermission = _allowed;
    emit RequestGAPINCheckFulfilled(_requestId, currentPermission);
    if (currentPermission) {
        //*********************************************************************
        // TO DO THE SPECIAL ACTION
        //*********************************************************************
        //currentPermission = false;
    }
  }

The Boolean type _allowed parameter is where the Chainlink node will input the 2FA authorization gathered from making the HTTP POST request. Let’s go over what each adapter does:

bridge

The Chainlink node will make an HTTP POST request.
The HTTP POST request that we want to make in the parameters is passed to the req variable. This variable is assigned in the requestGAPINCheck function as part of the smart contract code (you can see above in this section).

eq.add("customerid", _customerId);
req.addInt("hashedpin", _hashedpin);

jsonparse

Once the node makes the HTTP POST request, it will go through the JSON and find only the value that we want. Storing the entire return of the HTTP POST request would be really expensive, as the more you store on the Ethereum blockchain the more gas fees you have to pay. So, we want to return as little as possible.

req.add("path", "result");

This will condense the return of the HTTP POST request to just the value true, from the JSON of the returned value ( {“result”:true}).

ethbool

You’ll notice in our code above that we don’t pass any parameters to this adapter; that’s because we don’t need to. This adapter just converts our answer of true to the Solidity readable format.

ethtx

This one also doesn’t need any parameters. This is the adapter that actually posts the data back on-chain using sendChainlinkRequestTo method.

A complete working version of the contract above is available on GitHub. This implementation is currently connected to a demo 2FA API server for development and testing purposes. To modify it to a production environment and connect to an actual 2FA service, we need to update the job specification to one running on a 2FA External Adapter pointing to the live 2FA production servers.

If you need more technical details about the External Adapter, you can view it on market.link.

Summary

Using the Chainlink Network and its versatile External Adapter functionality, we’ve demonstrated how to integrate a smart contract with a 2FA service. Its integration gives the user an extra layer of security in smart contracts while only using hashed data on-chain.

This demo opens up many interesting potential use cases for smart contracts and service integrations that may require 2FA as an extra layer of security by companies managing sensitive data.

Learn More

If you’re a developer and want to connect your smart contract to existing data and infrastructure outside the underlying blockchain, reach out here, visit the Chainlink developer documentation, or get testnet LINK.

Website | Twitter | Discord | Reddit | YouTube | Telegram | Events | GitHub | Price Feeds | DeFi

Need Integration Support?
Talk to an expert
Faucets
Get testnet tokens
Read the Docs
Technical documentation