Introduction

LoQ (PC Login QR) is a feature to identify and authenticate a user on a web-site just with QR-code on login page. User does not have to provide his login, password or any type of one-time passwords.

User's scenario is just to scan a QR-code with his mobile app and to confirm login operation.

At the same time user will be identified with their identifier in the mobile app, authentication will be processed with digital signature process using PC transaction confirmation feature.

How it works you can see at PC Demo page.

login_page.png

Idenitification and Authentication workflow

There are at least 8 components, which participate in user's identification and authentication process with LoQ feature.

As a result, web-application back-end will receive from LoQ components PC User ID and PC Transaction ID of identified and authenticated user.
After this web-application can match PC User ID with user's record in their database and proceed with user’s authorization.

Prerequisites

  1. LoQ feature must be activated and installed in a customer’s infrastructure (or cloud-based installation can be used)
  2. Web-application must be linked with PC (web-application users must use PC for transaction confirmations)

Workflow

The workflow is shown on the Figure 1 (it's recommended to enlarge the Figure).

LoQ_workflow.png

Workflow Participants

Name              Functions
WebApp back-end WebApp back-end processes users' requests. In this workflow it must request LoQ JS from LoQ Internal component, place it into login page and provide it to a user
Web Browser Web browser on the user's side will process LoQ JS, draw QR-code to login and, after a user will be identified and authenticated, will redirect a user to authorized zone
LoQ JS LoQ JS is a java-script that does following:
- request a QR-code and with Session Params from LoQ External with specified interval (by default - every 1 minute)
- check with LoQ External if a user was already identified and authenticated with specified interval (by default - every 1 second)
- recieve and re-draw QR-code on the login page or make a redirect to WebApp authorization link after a user is identified and authenticated
LoQ External Component to interact with LoQ JS and PC Mobile SDK. Should be placed in DMZ with access from/to Internet
LoQ Internal Component to interact with WebApp back-end and PC Server, produces and processes Session Params during user identification and authorization. Makes a callback to WebApp back-end to pick the PC User ID of identified and authenticated user in the Session. Should be placed in internal network without internet connection
PC Server Component to create and confirm transaction to authenticate a user
Mobile App Calls PC Mobile SDK methods, provides UI for a user on a mobile phone
PC Mobile SDK Processes LoQ workflow on a mobile phone side:
- reads QR-value with Session Params
- makes a request to LoQ External with specified PC User ID
- processes transaction confirmation (digitally signing) to authenticate a user

Workflow steps (by the schema)

  1. When WebApp back-end receives a request for login page (a new user opens a web session), it calls LoQ Internal for a LoQ JS and specifies need parameters: PC system_id, login message, url to make a callback when user will be identified and authenticated and url to redirect the Web Browser into authorized zone.
    LoQ JS will contain all of the needed information to interact with LoQ External, draw the QR-code for login and check if a user was already authenticated.
    • WebApp back-end should realize callback endpoint with callback_url to receive data about identified and authenticated PC User and PC Transaction ID. Based on this information WebApp back-end should authorize a user.
    • WebApp back-end should realize redirect endpoint with redirect_url. To this address web browser will be redirected by LoQ JS after user authentication. Before that callback will be sent to callback_url.
  2. WebApp back-end embeds LoQ JS into login page and sends it to user’s Web browser.
  3. From this step, LoQ JS starts to interact with LoQ External, draws and renews QR-code for login and checks if a user was authenticated.
  4. On each request from LoQ JS LoQ External checks session data, renews QR-code if needed and sends redirection in case of user was authenticated.
  5. After QR-code was shown in the Web browser Mobile App can scan this QR-code to start identification and authentication process.
  6. QR-code value (decoded value) should be provided to PC Mobile SDK, after this authentication process can be started:
    • call PCLogin.importFromJSON(…)
    • determine if one of registered PC Users can be used for login (users are registered in the same PC System)
    • request Login Transaction to authenticate a user
  7. Based on the request from PC Mobile SDK with user’s identifier LoQ External makes a request to LoQ Internal for create PC Transaction with PC Server. This transaction must be signed to authenticate a user.
  8. PC Transaction ID will be provided via LoQ Internal and LoQ External to PC Mobile SDK.
  9. After this PC Mobile SDK requests transaction data and provides them to Mobile App to show to a user.
  10. If a user agrees to login, Mobile App with PC Mobile SDK signs the transaction.
  11. After PC Transaction was signed and verified, PC Server sends callback to LoQ Internal.
  12. LoQ Internal sends callback to WebApp back-end with PC User ID and PC Transaction ID in the current session.
  13. WebApp back-end should mark by internal rules that
    • in the session with specified session id
    • user with specified pc user id
    • authenticated by signing transaction with specified pc transaction id
  14. On the next request from LoQ JS LoQ External will answer that Web browser should be redirected to redirect_url with session id parameter.
  15. LoQ JS redirects browser to WebApp back-end’s redirect_url, WebApp back-end receives from Web browser session id.
  16. WebApp back-end checks which user was identified and authenticated in the session with specified session id (step 13).
  17. If a user was found, then WebApp back-end authorizes web session to access to authorized zone.
  18. Optionally, WebApp back-end can retrieve all of the information about transaction confirmation (digital signature value, mobile device data, transaction details and so on) from PC Server with specified transaction id.

Integration and API

Get LoQ JS

Get LoQ JS

endpoint: {{loq-internal-url}}/system/loginUser
method: POST
content-type: application/json

Request

{
    "systemId" : "{{pc-system-id}}",
    "callback" : "{{callback-url}}",
    "redirect" : "{{redirect-url}}",
    "sessionTimeout" : 6000,
    "qrTimeout" : 600,
    "size" : 300,
    "text": "Please, confirm your login to the best web site",
    "textRenderType": "raw",
    "qrUrlPrefix": "paycontrol",
    "deepLinkPrefix": "paycontrol",
    "deepLinkOnly": false,
    "pcTransactionAppExtra": "{\"app_callback_deeplink\": \"https://abs.absnet.loc/login_success\"}"
}

Response sample with JS-script and Session ID

{
    "jsScript": "[JS will be here (see sample below)]",
    "sessionId": "7db47b87-8107-4d25-a967-9210e0d50b20]"
}

Default JS for LoQ

// LoQ JS Script
getQr = function () {
    var xhr = new XMLHttpRequest();
    var urlLoq = 'https://dev.paycontrol.org/api5/loqr/ext/site/status';
    var body = {
        sessionId: "1e9a02b9-8827-4df7-a518-923007430872"
    };

    var loginQR;
    var loginDeepLink;
    var isRefreshRequired;

    makeRequest();

    function makeRequest() {
        let data = JSON.stringify(body);
        let response;

        xhr.open('POST', urlLoq);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.setRequestHeader('Cache-Control', 'no-cache');
        xhr.setRequestHeader('Pragma', 'no-cache');
        xhr.send(data);

        xhr.onload = function (e) {
            if (this.status === 200 || this.status === 201) {
                response = JSON.parse(e.target.response);
                isRefreshRequired = response.isRefreshRequired;
                loginQR = response.loginQR;
                loginDeepLink = response.loginDeepLink;

                checkQR(response);
                console.log(response);
            } else {
                console.log(e.target.onerror);

            }

            setTimeout(makeRequest, 1000);
        }
        ;
    }

    function checkQR(response) {
        if (response.redirect != null) {
            loginQR = null;
            document.getElementById('pc_lo_qr').src = "";

            var redirectForm = document.createElement('form');
            var sessionId = document.createElement('input');

            document.body.append(redirectForm);
            redirectForm.append(sessionId);
            redirectForm.setAttribute('action', response.redirect.url);
            redirectForm.setAttribute('method', 'post');
            redirectForm.setAttribute('hidden', 'true');
            sessionId.setAttribute('name', 'sessionId');
            sessionId.setAttribute('id', 'sessionId');
            sessionId.setAttribute('type', 'hidden');
            sessionId.setAttribute('value', response.redirect.sessionId);
            redirectForm.submit();
        }

        if (response.isRefreshRequired || typeof (loginQR) == undefined) {
            loginQR = response.loginQR;
            loginDeepLink = response.loginDeepLink;
            document.getElementById('pc_lo_qr').src = "data:image/gif;base64," + loginQR;

            if (null != loginDeepLink) {
                document.getElementById('pc_lo_deeplink').href = loginDeepLink;
            }
        }
    }
};

This request is to generate JS-script with all of the params to work on the web browser side.

Parameter Mandatory Description
systemId yes PC System ID
callback yes Callback url to provide information about identified and authenticated PC User
redirect yes Redirect
sessionTimeout yes Login session life time in seconds. After this time session can not be used to login
qrTimeout yes QR-code life time. After this time QR-code will be regenerated automatically by LoQ External
size yes QR-code size in pixels
text no Login transaction text. This text will be used to create PC Transaction and should be shown to a user in mobile app. If the text not specified then "empty" transaction wil be created. Good practice is to include in the text information about Operation System, Browser version and Login Time.
textRenderType no Method to render the text in mobile app. Can be raw or markdown. Default value is raw
qrUrlPrefix no If this value is set, then deep-link with specified proto (prefix) will be encoded into QR-code. It will be useful in case your app can handle deep-links and a user will use qr-scanner to scan a QR (for example, Camera App on iOS).
Deep-link in QR will look like [qrUrlPrefix]://loq?login_json=[json to be provided to PC Mobile SDK]
deepLinkPrefix no If this value is set, then, in addition to QR code, LoQ will generate deep-link to be shown on mobile version of your login page. It means, that your login page can show QR code for desktop version and deep-link for mobile version. A user can tap on the deep-link to start login process (your mobile app should handle deep-links).
Deep-link in QR will look like [deepLinkPrefix]://loq?login_json=[json to be provided to PC Mobile SDK]
Be noticed, that JS will place QR and deep-link into HTML DOM separately (see default JS).
deepLinkOnly no If this value is true, then QR-code will be not generated, but deep-link only will be provided. It's useful if you request login from mobile web-browser
pcTransactionAppExtra no This value will be translated to PC Server's Transaction during login process. And this value will be provided to a mobile app. See Create Transaction Request
This value can contain variable %SESSION_ID% in addition to variables, set by PC Server.

Response will return JS-script that should be embedded into login-page in <script></script> html-tag.

Login-page content

Example JS-code to launch LoQ

// if LoQ JS was embedded successfully
if (typeof getQr === "function") { 
    getQr();
}

Default LoQ JS tries to find pc_lo_qr object with type img in HTML DOM structure and to insert into src property value of recieved QR-code.
If deepLinkPrefix is set, then Default LoQ JS tries to find pc_lo_deeplink object with type a and insert deep-link into href property.

To launch the interaction process between LoQ JS and LoQ External login-page must execute function with default name getQr (see example).

LoQ JS can be tuned or replaced (if required) during LoQ installation process. In this case, functions' or objects' names can be different.

Callback receiver requirements

Callback content

endpoint: {{callback_url}}
method: POST
content-type: application/json
{ 
   "pc_callback":{ 
      "loqr_callback":{ 
         "sessionId":"25552734-a388-4f30-8517-2b67598f2f75",
         "pcUserId":"c1d656e9-481b-4244-ade1-f36ec10eff25",
         "transactionId":"bf6c447a-0f82-4bd3-a49f-0c460bea4fcc"
      },
      "type":"loqr_callback",
      "result":{ 
         "error_message":"Success",
         "error_code":0
      },
      "version":3
   }
}

After a user will be identified and authenticated LoQ Internal will send callback to WebApp back-end to callback_url, specified in Get LoQ JS request.

Parameter Mandatory Description
pc_callback.type yes Value must be loqr_callback
pc_callback.loqr_callback yes Data about identified and authenticated user
pc_callback.loqr_callback.sessionId yes LoQ session id used to authenticate a user
pc_callback.loqr_callback.pcUserId yes Authenticated PC User ID
pc_callback.loqr_callback.transactionId yes PC Transaction ID which was signed by authenticated user.
pc_callback.result yes Error description
pc_callback.version yes Callback version

Redirect receiver requirements

Default script to make a redirect

// clear QR-code value
document.getElementById('pc_lo_qr').src = "";

// create html-form
var redirectForm = document.createElement('form');
document.body.append(redirectForm);
redirectForm.setAttribute('action', response.redirect.url);
redirectForm.setAttribute('method', 'post');
redirectForm.setAttribute('hidden', 'true');

// add input with name sessionId
var sessionId = document.createElement('input');
sessionId.setAttribute('name', 'sessionId');
sessionId.setAttribute('id', 'sessionId');
sessionId.setAttribute('type', 'hidden');

// set sessionId value to recevied from LoQ External value
sessionId.setAttribute('value', response.redirect.sessionId);

redirectForm.append(sessionId);

// submit the form to redirect
redirectForm.submit();

After a LoQ JS will receive information about user was authenticated, it will redirect Web browser to redirect_url specified in Get LoQ JS request.

Default LoQ redirection method is js-script that embeds html-form into HTML DOM model and submits them.
Before this script clears QR-code value.

So, if you are using default redirection method, redirect receiver on WebApp back-end side should process request with application/x-www-form-urlencoded content-type.

JS-script to proceed the redirection can be tuned or replaced if needed during LoQ installation process.

Mobile App Requirements

Mobile App should realize LoQ process as shown on the schema.

Login process from mobile app point of view are managed with PCLogin class.

The sample algorithm of logging in with QR-code (or deep-link) is the following:

  1. Scan QR code (or handle deep-link) and extract json containing all required data
  2. Call importFromJson(String, GetLoginDataCallback) to construct PCLogin object. Constructed object will be returned as parameter of success() method.
  3. Call suggestUsers() on constructed object to get keys which can be used to log in
  4. After choosing a key call requestLoginTransaction(PCUser, GetLoginTransactionCallback) with a selected PCUser from one of returned by suggestUsers()
  5. After PCLogin.GetLoginTransactionCallback.success(PCTransaction) is invoked, you may show transaction data in the app. Note, that the PCTransaction.getTransactionText() can either return null or text of transaction. Do not try to sign this transaction manually, instead call either confirm(PCGeneralCallback) to finish logging in or decline(PCGeneralCallback) to cancel login operation.

You can find more detailed information in PC Mobile SDK.

At the same time reference PC Mobile Application can be used.

Infrastructure integration

Components interaction

LoQ contains of two parts: LoQ External and LoQ internal.

Roles of this parts you can find in the workflow description.

Login workflow participants interaction diagram you can find on Figure 2.

LoQ_infrastructure.gif

Server component supply options

LoQ server components are supplied as Java application (jar-file) and instruments to run as service (linux- or windows-based).

LoQ uses Redis cache server to store data about active sessions. Redis should be installed to be accessible from LoQ Internal and LoQ External components.

Typical machine components

A typical machine (or a container) consists of the following installed components:

Component Description
Operating system Linux-based
Application server operation environment Java 8/11

All the LoQ components are launched automatically with the operating system. No manual settings for start/shutdown is required.

If neither a physical nor virtual servers are supplied, the OS preparation is carried out by the customer. Preparation includes:

  • Installation of the operating system;
  • Proper configuration of DNS records;
  • Installation of the Java Runtime Environment;
  • Preparing TLS certificates (if necessary).

The following alternative components can be used:

Component Description
Operating system Microsoft Windows
Application server operation environment Java 8/11

Resiliency and scaling

Resiliency and scaling options are equals to PC Server components.

Installation and startup

Under docker

At first you need to deploy and grant access to same Redis database for Internal and External LoQR components.

For start under docker you need to modify to change entrypoint script file for openjdk.

Dockerfile

FROM openjdk:17
COPY ./docker-entrypoint.sh /
WORKDIR /opt/loqr/
ENTRYPOINT ["/docker-entrypoint.sh"]

Entrypoint script executes java with last (in alphanumeric order) jar-file in directory in workdir and name of a configuration file.

docker-entrypoint.sh

#!/bin/sh
JARFILE=$(ls *.jar | tail -1)
java -jar $JARFILE application.yml

There is templates of application.yml files. You need to specify the next values:

  • loqr.pc.url — PCS API endpoint.
  • loqr.url — address of Internal LoQR component. Uses for callback from PC when login transaction is confirmed.
  • loqr.urlExternal — external address for External LoQR component. It will added to JavaScript for pulling session status and updates of QR code.
  • spring.redis — address of Redis database.
  • loqrExternal.loqr — internal address of Internal LoQR component. Uses for call by Internal LoQR for login transaction creation.
  • loqrExternal.url — external address for External LoQR component. This address will be added to QR, and will be used for communication mobile SDK with External LoQR module.
  • loqrExternal.logoQRPath — optional. Path to image file for adding to QR as a logo.

application.yml for Internal LoQR component.

server:
  port: 8098
  address: 0.0.0.0
loqr:
  parametrs:
    qr:
      width: 120
      height: 120
      typeImg: "gif"
  pc:
    url: "http://pc:8080/pc-api/"
  url: "http://loqrint:8098"
  urlExternal: "https://example.com/loqr/ext"
  timeoutInSecond:
    QR: 120
    session: 600
spring:
  redis:
    host: redis
    port: 6379
    database: 1
    jedis:
      pool:
        min-idle: 2
        max-active: 500
        max-idle: 50
        max-wait: 30
#logging:
#  level:
#    tech.paycon: DEBUG

application.yml for External LoQR component

server:
  port: 8099
  address: 0.0.0.0
loqrExternal:
  loqr: "http://loqrint:8098"
  logoQRPath: "pc_logo.png"
  url: "https://example.com/loqr/ext"
  timeoutInSecond:
    QR: 120
    session: 600
spring:
  redis:
    host: redis
    port: 6379
    database: 1
    lettuce:
      pool:
        min-idle: 8
# logging:
#   level:
#    tech.paycon: DEBUG

docker-compose.yml

version: '3.7'
services:
  loqrint:
    container_name: "loqrint"
    volumes:
      - "/opt/pc/loqr/int/:/opt/loqr:Z"
    restart: unless-stopped
    build: images/loqr
    depends_on:
      - redis

  loqrext:
    container_name: "loqrext"
    volumes:
      - "/opt/pc/loqr/ext/:/opt/loqr:Z"
    restart: unless-stopped
    build: images/loqr
    depends_on:
        - redis