One-Time Password Authentication

The authentication library incorporates two-factor authentication using OTP (currently TOTP conforming to RFC6238 TOTP standards is supported) and QR-codes. In case you would like to do without all the library code unrelated to OTP you can load the OTP helper instead and deal with the helper handlers.

Note: OTP authentication requires LiveCode's QR Code Generator Library. Please read about integration of the QR code library in chapter OTP Helper.

Activation

To activate two-factor authentication set sAuthenticationConf["otpEnabled"] in application/config/authentication.lc to TRUE.

OTP Authentication Table

In order to use two-factor authentication you need one more table in addition to the 4 tables defined in chapter Authentication Tables. This is a child table of the "users" table. Name this table "otp" according to default settings otherwise you need to adjust the authentication settings.

The MySQL version:

# Table structure for otp
# ------------------------------------------------------------

CREATE TABLE `otp` (
  `id` mediumint(8) unsigned NOT NULL auto_increment,
  `userId` mediumint(8) unsigned NOT NULL,
  `key` tinytext character set utf8 collate utf8_unicode_ci NOT NULL,
  `time` int(11) unsigned default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

The PostgreSQL version:

# Table structure for otp
# ------------------------------------------------------------

CREATE TABLE "otp" (
  "id" SERIAL NOT NULL,
  "userId" integer NOT NULL REFERENCES "users" ON UPDATE CASCADE ON DELETE CASCADE,
  "key" text NOT NULL,
  "time" int,
  PRIMARY KEY("id"),
  CONSTRAINT "check_id" CHECK(id >= 0),
  CONSTRAINT "check_userId" CHECK("userId" >= 0),
);

The SQLite version:

# Table structure for otp
# ------------------------------------------------------------

CREATE TABLE "otp" (
  "id" integer NOT NULL ON CONFLICT ABORT PRIMARY KEY AUTOINCREMENT,
  "userId" integer(8) NOT NULL,
  "key" text NOT NULL,
  "time" integer(11) DEFAULT NULL,
  CONSTRAINT "Foreign_UsersID" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

OTP Authentication Handler Reference

rigAuthGenerateQR(pAccount)

Use this function to create a QR code based on the otpauth:// URI scheme and get the associated base32 encoded shared secret.

Parameters

The function returns an array with indices: "key" containing the base32 encoded shared secret, and "qr" containing the QR code ready to be displayed in a web page. If QR code generation fails empty array values are returned.

Example:

put rigAuthGenerateQR() into tQRa
put tQRa["key"] into gData["key"]
put tQRa["qr"] into gData["qrCode"]

rigAuthOTPkeysMatch(pChallenge)

This function allows you to compare user supplied authentication code (OTP) with a generated code based on the user's database values.

Parameters

The function returns TRUE in case both codes match, otherwise it returns FALSE.

Example:

put rigVarPost("authcode") into tAuthCode

if tAuthCode <> False then
  # COMPARE tAuthCode WITH GENERATED CODE
  if rigAuthOTPkeysMatch(tAuthCode) is TRUE then
    # REDIRECT TO SUCCESS PAGE
    rigRedirect "otpCheckPassed"
  else
    -- your code to display the OTP authentication form again
  end if
end if

rigAuthUserHasOTP(pUserID)

Use this function to check if a user has set up OTP authentication.

Parameters

The function returns "pendingOTPsetup" in case the user has requested a QR code to set up two-factor authentication, but has not yet transferred a valid one-time password. In case the setup was successfully finished the function returns TRUE. If there is no OTP entry for the particular user the function returns FALSE.

Example:

# FORM VALIDATION
if rigFormValidRun("auth/login") is TRUE then

  # CHECK IF USER IS LOGGING IN
  put FALSE into tRemember
  # CHECK IF THERE IS A POST VARIABLE remember
  put rigVarPost("remember[]") into tPostRemember -- remember check box

  if tPostRemember <> FALSE then
    # CHECK VALUE
    if tPostRemember[1] is 1 then
      put TRUE into tRemember
    end if
  end if

  get rigAuthLogin(rigVarPost("identity"), rigVarPost("password"), tRemember)

  # IF LOGIN IS SUCCESSFUL CHECK IF USER HAS SET UP TWO-FACTOR AUTHENTICATION
  if it is TRUE then
    if rigAuthUserHasOTP() is TRUE then
      # REDIRECT TO OTP FORM
      rigRedirect "otp"
    else
      # REDIRECT TO PROTECTED PAGE
      put rigAuthMessages() into tMessages
      rigSetSessFlashdata "message", tMessages
      rigRedirect "/protected"
    end if

  else
    # IF LOGIN IS UNSUCCESSFUL REDIRECT BACK TO THE LOGIN PAGE
    put rigAuthErrors() into tErrors
    rigSetSessFlashdata "message", tErrors
    rigRedirect "auth/login"
  end if

else
  # THE USER IS NOT LOGGING IN, SHOW THE LOGIN PAGE
  # SHOW VALIDATION ERRORS OR FLASH DATA IF THERE IS ANY

end if

rigAuthClearOTP(pUserID)

This function deletes a user's OTP database entry.

Parameters

The function returns TRUE in case the operation was successful, otherwise it returns FALSE.

rigAuthLoggedIn()

Check to see if a user is logged in.

This function returns TRUE if the user is logged in, otherwise it returns FALSE.

In case two-factor authentication is activated the function may return "pending" if the user logged in successfully but has still not transferred a valid one-time password. If the user logged in successfully and requested a QR code to set up two-factor authentication but still not transferred a valid one-time password the function returns "pendingOTPsetup".

Example:

# CHECK IF USER IS LOGGED IN, CAN BE "pending" IF USER HAS SETUP OTP
# AND HAS NOT YET SUPPLIED A VALID OTP
put rigAuthLoggedIn() into tLoggedIn

if tLoggedIn is FALSE then
  # REDIRECT TO LOGIN PAGE AND KEEP FLASHDATA IN CASE THERE IS ANY
  if rigSessFlashdata("message") <> FALSE then
    rigKeepSessFlashdata "message"
  end if

  rigRedirect "auth/login"

else if rigAuthIsAdmin() is FALSE then

  if (tLoggedIn <> "pending") and (tLoggedIn <> "pendingOTPsetup") then
    # REDIRECT TO HOME PAGE BECAUSE USER IS NOT ALLOWED TO VIEW ADMIN CONTENT
    rigRedirect "/"
  else
    # USER HAS NOT YET SUPPLIED A VALID OTP
    if tLoggedIn is "pendingOTPsetup" then
      # OTP AUTHENTICATION SETUP INCOMPLETE
      rigRedirect "otp/setup"
    else
      rigRedirect "otp/"
    end if
  end if

else -- if tLoggedIn is FALSE then
  # ADMIN CONTENT
  #
  if tLoggedIn <> "pending" then
  -- your code . . .
  end if
end if