Send an One Time Password (OTP) via the Twilio SMS gateway

Problem this snippet solves:

This snippet makes it possible to send an One Time Password (OTP) via the Twilio SMS gateway. This snippet uses iRuleLX and the node.js twilio package to interact with the Twilio API.

How to use this snippet:

Prepare the BIG-IP

  • Provision the BIG-IP with iRuleLX.
  • Create LX Workspace: workspace_twilio
  • Add iRule: irule_twilio
  • Add Extension: extension_twilio
  • Add LX Plugin: plugin_twilio -> From Workspace: workspace_twilio

Install the node.js twilio module

# cd /var/ilx/workspaces/Common/workspace_twilio/extensions/extension_twilio
# npm install twilio --save
  /var/ilx/workspaces/Common/workspace_twilio/extensions/extension_twilio
  └─┬ twilio@3.13.0 
  (lots and lots of modules follow...)
#

iRule

To make it works, you need to install the irule on the Virtual Server that publish your application with APM authentication.

Access Profile

If you already have an existing access profile, you will need to modify it and include some additional configuration in your VPE. If you have no access profile, you can starts building your own based on the description we provide below.

Configuring the Visual Policy Editor

The printscreen below is a minimal Visual Policy Editor used to make Twilio OTP Authentication works properly:

IE – Twilio

This is an Irule Event with the ID set to

twilio
. This will trigger the irule_twilio iRule to come into action.

EA - Twilio Status

This is an Empty Action with two branches. The branch named "successful" contains the following expression :

expr { [mcget {session.custom.twilio.status}] starts_with "SM" }

Message Box

This is a Message Box that will inform the user that there was a failure sending the One Time Password. For example:

Oops, something went wrong. Please login again.

irule_twilio

when ACCESS_POLICY_AGENT_EVENT {
    if { [ACCESS::policy agent_id ] eq "twilio" } {

        set username "[ACCESS::session data get session.logon.last.username]"
        set generatedOTP "[ACCESS::session data get session.otp.assigned.val]"
        set telephoneNumber "[ACCESS::session data get session.ad.last.attr.telephoneNumber]"

        # The sender of the message. From a valid Twilio number.
        set sender "+31123456789"

        if {[info exists username] && ($username eq "")} {
            log local0. "Error: username variable is empty; no OTP sent."
            return
        }

        if {[info exists generatedOTP] && ($generatedOTP eq "")} {
            log local0. "Error: generatedOTP variable is empty; no OTP sent for user $username."
            return
        }

        if {([info exists telephoneNumber] && $telephoneNumber eq "")} {
            log local0. "Error: telephoneNumber variable is empty; no OTP sent for user $username."
            return
        }

        set rpc_handle [ ILX::init plugin_twilio extension_twilio]
        if {[ catch { ILX::call $rpc_handle sendOTP $generatedOTP $telephoneNumber $sender } result ] } {
            log local0. "sendOTP failed for telephoneNumber: $telephoneNumber, ILX failure: $result"
            return
        }

        log local0. "Twilio status for user $username ($telephoneNumber): $result"
        ACCESS::session data set session.custom.twilio.status $result
    }
}

Code :

// Import the f5-nodejs module.
var f5 = require('f5-nodejs');
 
// Create a new rpc server for listening to TCL iRule calls.
var ilx = new f5.ILXServer();

const accountSid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // Your Account SID from www.twilio.com/console
const authToken = 'your_auth_token';   // Your Auth Token from www.twilio.com/console

const client = require('twilio')(accountSid, authToken);
 
ilx.addMethod('sendOTP', function(req, res) {
    var generatedOTP = req.params()[0];
    var telephoneNumber = req.params()[1];
    var sender = req.params()[2];
    var message = 'Your OTP is: ' + generatedOTP;
    
    client.messages.create({
        body: message,
        to: telephoneNumber,  // Text this number
        from: sender // From a valid Twilio number
    })
    .then(function(message) {
            return res.reply(message.sid);
    });
    
});
 
// Start listening for ILX::call and ILX::notify events.
ilx.listen();

Tested this on version:

13.0
Updated Jun 06, 2023
Version 2.0