Two-factor OTP authentication

For additional security, it is possible to turn on two-factor one-time-password authentication for the specified or all users in the setup.

Note

Two-factor authentication is not supported by the default mobile client evaHI. Consider creating dedicated mobile user accounts.

System setup

Services

Two-factor authentication is provided by OTP 2nd-Factor authentication service and supported by:

OTP authentication

To start using OTP, the above services require otp_svc configuration parameter set to the deployed OTP service ID. Note that if the default domain is configured in Active directory auth service, user and user@domain are processed as two different users.

Excluding user accounts from OTP

Certain user accounts can be excluded from 2FA with:

Resetting OTP for a user account

If a user has lost his OTP secret, the following service RPC call can be used to reset it. E.g. let us use eva-shell:

eva svc call eva.aaa.otp otp.destroy i=USER

User authentication

API

Use generic authentication functions to authenticate users. No special RPC/HTTP calls are required.

If a user is correctly authenticated with the password, but there is no OTP code provided, the authentication service returns an error with code -32022 (ACCESS_DENIED_MORE_DATA_REQUIRED) and the following message:

|OTP|<SVC_ID>|PAYLOAD

where <SVC_ID> is the OTP service ID and the payload is:

  • REQ OTP code is required (not provided)

  • INVALID OTP code is provided but invalid

  • SETUP=<SECRET> OTP setup is required, use the secret provided

To finish authentication, repeat the login process again, with an extra parameter in the payload:

{
  // HTTP API or EAPI payload
  "xopts": { "otp": "CODE" }
}

Authenticator programs

OTP 2nd-Factor authentication service provides the default HMAC-based Time-OTP with SHA-1 checksums and 6-digit code length.

Such TOTP is fully supported by the following programs out-of-the-box:

and other compatible.

HMI application example

OTP is supported out-of-the-box by EVA JS Framework, starting from the version 0.3.38.

Here is an example of two-factor login logic in HMI web application:

<html>
  <head><script type="text/javascript" src="eva.min.js"></script>
  <body>
    <div id="status"></div>
    <div id="qr_container" style="width: 200px; height: 200px; display: none">
      <canvas id="qr"></canvas>
    </div>
    <div id="login_form">
      <form onsubmit="process_login(event)">
      User: <input type="text" id="login"><br>
      Password: <input type="password" id="password"><br>
      <input type="submit" value="Login">
      </form>
    </div>
    <div id="otp_form" style="display: none">
      <form onsubmit="process_otp(event)">
      OTP: <input type="text" id="otp_code"><br>
      <input type="submit" value="Continue">
      </form>
    </div>
    <script type="text/javascript">
      function process_login(event) {
        event.preventDefault();
        set_status("Logging in...");
        $eva.login = document.getElementById("login").value;
        $eva.password = document.getElementById("password").value;
        hide("login_form");
        $eva.start();
        return false;
      }
      function process_otp(event) {
        event.preventDefault();
        set_status("Logging in...");
        $eva.login_xopts = { otp: document.getElementById("otp_code").value };
        hide("otp_form");
        $eva.start();
        return false;
      }
      function hide(id) {
        document.getElementById(id).style.display = "none";
      }
      function show(id) {
        document.getElementById(id).style.display = "block";
      }
      function focus(id) {
        document.getElementById(id).focus();
      }
      function clear(id) {
        document.getElementById(id).value = "";
      }
      function set_status(msg) {
        document.getElementById("status").innerHTML = msg;
      }

      focus("login");
      $eva.api_version = 4;
      $eva.on("login.success", () => {
        // hide QR container in case if OTP setup process was going
        hide("qr_container");
        set_status("Logged in as " + $eva.server_info.aci.u);
        // do not forget to clear the password in DOM and framework variables
        clear("password");
        $eva.password = "";
        $eva.login_xopts = null;
      });
      $eva.on("login.failed", (err) => {
        set_status(`Login failed: ${err.message} ${err.code}`);
        show("login_form");
        clear("password");
        focus("password");
      });
      $eva.on("login.otp_required", () => {
        show("otp_form");
        set_status("OTP code required");
        focus("otp_code");
      });
      $eva.on("login.otp_invalid", () => {
        show("otp_form");
        set_status("Invalid OTP code entered");
        clear("otp_code");
        focus("otp_code");
      });
      $eva.on("login.otp_setup", (msg) => {
        show("qr_container");
        show("otp_form");
        set_status("Scan this code with an authenticator app");
        $eva.otpQR("qr", msg.value);
        focus("otp_code");
      });
    </script>
  </body>
</html>