QR Code Login

Introduction

In this guide you will learn how QR Code Login can be performed programmatically via REST APIs using IBM Security Verify. QR Code Login allows first factor user authentication by simply scanning a QR Code with a registered authenticator (usually running on a mobile device).

For more background, check out the check out QR Code Login concepts.

Pre-requisites

An end user that will authenticate using QR Code Login must have the IBM Verify app on their mobile device and have it registered against their account. Check out the Knowledge Center for information on this.

The API client used by the application must have the following permissions in IBM Security Verify:

  • Read authenticator configuration (to get authenticator ID)
  • Authenticate any user (to run login flow)
  • Read users and groups (to lookup user data)
    For details on creating an API client, see Create an API client.

The application must have acquired an Access Token using the Client Credentials flow.

Variables

The following variables are needed to run the flow described in this guide:

VariableExample ValueDescription
tenant_urltenant.verify.ibm.comThe URL of your IBM Security Verify tenant.
access_tokeneWn4Z5xChc3q9B9dqbGgFlsHDh7uhAOnmNeKW5EzThe access token obtained from the token endpoint.

Look up Authenticator Client ID

An IBM Security Verify tenant can support multiple Registration Profiles for QR Code Login and Mobile PUSH authenticator apps. In the APIs a Registration Profile is known as an Authenticator Client. For this guide you will use the default profile, Verify Profile. You will now lookup this Authenticator Client by name to get the ID that is needed to identify the authenticator client during QR Code Login initiation.

📘

Registration Profile ID

The Verify Registration Profile ID is available directly in the Verify Admin UI. If you prefer you could skip this lookup step and get the ID from the Admin UI instead.

curl -X GET "https://${tenant_url}/v1.0/authenticators/clients?search=name%20%3D%20%22Verify%20Profile%22" -H "Authorization: Bearer ${access_token}"
//Pre-requisites
//var axios = require('axios');
//var tenant_url = "Tenant URL";
//var access_token = "Access Token";

var request = {
  method: 'get',
  url: 'https://' + tenant_url
        + '/v1.0/authenticators/clients'
        + '?search=name = "Verify Profile"',
  headers: { 
    'Authorization': 'Bearer ' + access_token
  }
};

axios(request).then((response) => {
  var authclient_id = response.data.clients[0].id;
  console.log(authclient_id);
  
  //Next code here.
  
}).catch((error) => {
  console.log(error);
});

The JSON response to this call has the following format:

{
    "total": 1,
    "clients": [
        {
            "authorizationCodeLifetime": 60,
            "name": "Verify Profile",
            "refreshTokenLifetime": 31557600,
            "id": "f4bc8b91-8061-49bf-9c69-46ffcb0b3d7b",
            "enabled": true,
            "accessTokenLifetime": 3600
        }
    ],
    "limit": 200,
    "count": 200,
    "page": 1
}

You need the clients[0].id element from this response and store as authclient_id.

Initiate QR Code Login transaction

A QR Code Login transaction is initiated by calling the /v2.0/factors/qr/authenticate endpoint. The ID of the authenticator client must be provided in the profileId query string parameter.

curl -X GET "https://${tenant_url}/v2.0/factors/qr/authenticate?profileId=${authclient_id}" --header "Authorization: Bearer ${access_token}"
//Pre-requisites
//var axios = require('axios');
//var tenant_url = "Tenant URL";
//var access_token = "Access Token";
//var authclient_id = "Registration profile ID";

var request = {
  method: 'get',
  url: 'https://' + tenant_url 
          + '/v2.0/factors/qr/authenticate'
          + '?profileId=' + authclient_id,
  headers: { 
    'Authorization': 'Bearer ' + access_token
  }
};

axios(request)
.then((response) => {
  var qr_txnid = response.data.id;
  var qr_dsi = response.data.dsi;
  console.log(qr_txnid);
  console.log(qr_dsi);
  console.log("***Image***: " + response.data.qrCode);
  
  //Next code here.
  
}).catch((error) => {
  console.log(error);
});

The response has the following format:

{
    "id": "0ac803e1-fabe-43ff-bd69-d0b6deb2ad5e",
    "type": "qr",
    "created": "2021-01-18T12:21:22.257Z",
    "updated": "2021-01-18T12:21:22.257Z",
    "expiry": "2021-01-18T12:23:22.257Z",
    "state": "PENDING",
    "location": "https://tenant.verify.ibm.com/v2.0/factors/qr/0ac803e1-fabe-43ff-bd69-d0b6deb2ad5e",
    "profileId": "f4bc8b91-8061-49bf-9c69-46ffcb0b3d7b",
    "serviceName": "IBM Security Verify",
    "tenant": "tenant.verify.ibm.com",
    "lsi": "u7u44a0tcmo4b6zhg2t6geujd5iu61",
    "dsi": "8097upp060qu5kf1lw0wd42wy4ionp",
    "qrCode": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsAQAAAA...FTkSuQmCC"
}

From this response you need the id attribute which is the transaction ID (stored as qr_txnid). You will also need the dsi attribute (stored as qr_dsi) when querying the status of the transaction. The QR Code that needs to be displayed to the user is returned as a Base64-encoded image in the qrCode attribute (stored as qr_image).

Display QR Code to the user

If you are working in a web application, a base64-encoded image can be rendered directly by the browser. Use the following HTML snippet (in this case the snippet is from a NodeJS Express view using handlebars):

<img src='data:image/png;base64,{{qr_image}}' alt='qrcode' />

For testing purposes, you could also use an online utility to display the base64-encoded image so you can scan it. Two such tools are:

Poll for scan completion

The application must now poll for completion of the QR Code transaction. Completion will occur when the user scans the QR Code with a registered authenticator app.

Authentication is not required for the poll. However, the caller must know the transaction ID and the associated Device Session Identifier (dsi).

🚧

Polling from browser

It is possible to poll directly to IBM Security Verify from the browser process but this requires that the browser have the transaction ID and DSI. It also requires CORS to be set up to allow the cross-origin request.

It is usually better to have the browser poll the application, and have the application poll IBM Security Verify. In this way, the application maintains control of the flow.

The following code samples show the polling request. In the case of the NodeJS code, a loop is used to repeat the poll every 3 seconds while the state is "PENDING".

curl -X GET "https://${tenant_url}/v2.0/factors/qr/authenticate/${qr_txnid}?dsi=${qr_dsi}"
//Pre-requisites
//var axios = require('axios');
//var tenant_url = "Tenant URL";
//var qr_txnid = "QR Transaction ID";
//var qr_dsi = "QR Device Session Index";

function queryQRtxn() {

  var request = {
    method: 'get',
    url: 'https://' + tenant_url
            + '/v2.0/factors/qr/authenticate/'
            + qr_txnid
            + '?dsi=' + qr_dsi,
  };

  axios(request)
  .then((response) => {
    if(response.data.state == "PENDING") {
      setTimeout(queryQRtxn,3000);
    } else if (response.data.state == "SUCCESS") {
        var user_uuid = response.data.userId;
        console.log(user_uuid);

        //Next code here

    } else {
      console.log("QR Code validation failure");
    }
  }).catch((error) => {
    console.log(error);
  });
};

setTimeout(queryQRtxn,3000);
console.log("QR Transaction PENDING");

The response to the poll is similar to the initiation response except that it doesn't include the lsi, dsi, or qrCode attributes. The most important attribute is state. This indicates the state of the transaction.

A state of SUCCESS indicates that a user has been authenticated by the QR Code:

{
    "id": "0ac803e1-fabe-43ff-bd69-d0b6deb2ad5e",
    "userId": "642000EPOU",
    "type": "qr",
    "created": "2021-01-18T12:21:22.257Z",
    "updated": "2021-01-18T12:22:25.143Z",
    "expiry": "2021-01-18T12:23:22.257Z",
    "state": "SUCCESS",
    "updatedBy": "642000EPOU",
    "location": "https://tenant.verify.ibm.com/v2.0/factors/qr/0ac803e1-fabe-43ff-bd69-d0b6deb2ad5e",
    "profileId": "f4bc8b91-8061-49bf-9c69-46ffcb0b3d7b",
    "serviceName": "IBM Security Verify",
    "tenant": "tenant.verify.ibm.com"
}

The ID of the user is returned in the userId attribute (stored as user_uuid).

Look up user information

The QR Code Login poll response only includes the unique identifier (uuid) for the user that was authenticated. In order to get useful information about this user, the application can call the SCIM Users endpoint:

curl -X GET "https://${tenant_url}/v2.0/Users/${user_id}" --header "Authorization: Bearer ${access_token}"
//Pre-requisites
//var axios = require('axios');
//var tenant_url = "Tenant URL";
//var access_token = "Access Token";
//var user_uuid = "User UUID";

var request = {
  method: 'get',
  url: 'https://' + tenant_url 
          + '/v2.0/Users/' + user_uuid,
  headers: { 
    'Authorization': 'Bearer ' + access_token
  }
};

axios(request)
.then((response) => {
  var user_scim = response.data;
  console.log(user_scim);
  
  //Next code here.
  
}).catch((error) => {
  console.log(error);
});

The application now has the full user SCIM object for the user that authenticated with QR Login.

💎

Jon Harry, IBM Security