iRuleLX Solution for OAUTH JWT authenticated Salesforce Query

Summary

A customer recently asked if F5 BIG-IP Access Policy Manager (APM) could be used to authorize a user’s session based on a query to Salesforce. The answer to such questions is always yes when you have the power of iRules and the more expanded power of iRulesLX.

 

Requirement

Given a certificate generated and uploaded to Salesforce for user authentication, create a JWT and retrieve the bearer token. Using this token, access, and query the Salesforce API. Return the provided query results.

 

Discussion

TCL is the language of iRules and is extraordinarily powerful when combined with the event-driven capabilities of F5’s platform. Expand iRules with iRuleLX and you add node.js and JavaScript to our capabilities. One might ask, though, why use JavaScript when TCL is already available? In this case, it’s about going native and speaking the language directly. It’s also about leveraging provided code, so you don’t have to reinvent and support extensive code long term.

While brainstorming for a solution, I reviewed several paths. When you have the power of F5 and the myriad of capabilities, you often find there are many ways to get the wanted results.

A driving factor for me was the need to deliver an example of meeting the requirements ASAP to my customer. Since I discovered that Salesforce provides node.js modules in npm repositories, I could use Salesforce’s supported code and methods to perform the calls. This simplified the solution and code dramatically.

 

Prerequisites

You will need an F5 instance, of course, and one with iRules LX licensed and provisioned.

For more on iRules LX general concepts and provisioning, see these great DevCentral articles.

Getting started with iRules LX Part 1

Getting started with iRules LX Part 2

The following assumes your F5 has direct internet access, or you can set up npm for proxy access. Installation of npm packages in an off-line mode is out of scope, but you will find many discussions on doing such by internet search.

Generally, some coding experience or understanding is helpful. There are some basic command line requirements, so familiarity with the F5 terminal interface is also helpful.

The upload of a certificate to Salesforce for authentication purposes is covered by Salesforce documentation and isn’t discussed in this article.

 

Step 1. Create a Workspace. LTM -> iRules -> LX Workspaces

Add your new workspace called “sfDemo” and click “Finished”.

 

 

Add an extension:

Now we want to open the new Workspace. Click “Add Extension”

 

Extension name: “querysf”

 

Step 2 Replace the contents in index.js with this:

Click on the newly created “index.js” under the extension “querysf”. In the right-hand editor window, select and delete the existing contents and replace it with the following code:

//load up some libs
const jsforce = require('jsforce');
const jwt = require("salesforce-jwt-bearer-token-flow");
const f5 = require('f5-nodejs');

// create the connection to the org using jsforce from salesforce
// https://jsforce.github.io/start/
let conn = new jsforce.Connection();
	 
// load the private key for the token
let privateKey = require('fs').readFileSync('./keys/server.key', 'utf8');
	 
// Define the ilx server 
var ilx = new f5.ILXServer();
 
// Define the ilx method. call from irule as "getAuthoization" "userid"
ilx.addMethod('getAuthorization', function (req, res) {
    var userName = req.params()[0];
//    console.log(userName);
// Get a signed JWT from Salesforce JWT Bearer token flow
// https://www.npmjs.com/package/salesforce-jwt-bearer-token-flow
// we prepopulate the iss, aud, and load the private key to sign the JWT
// the sub is the user credentials that are passed to us from the iRule
   jwt.getToken({
       iss: "3MVG99OxTyEMCQ3gNp2PjkqeZKxnmAiG1xV4oHh9AKL_rSK.BoSVPGZHQukXnVjzRgSuQqGn75NL7yfkQcyy7",
       sub: userName,
       aud: "https://login.salesforce.com", 
       privateKey: privateKey
        
    }, function(err, response) {
        if (err) {
            console.error(err);
	            
        } else {
            conn.initialize({
                instanceUrl: response.instance_url,
                accessToken: response.access_token
	                
            });
// At this point, we have gotten an authorized JWT so we query the API for info we need
            conn.query('SELECT Id, Name FROM Account LIMIT 1', function(err, results) {
                if (err) {
                    console.error(err);
                    
                } else {
//                    console.log(results);
//                    console.log(`First record : ${results.records[0].Name} - ${results.records[0].Id}`);

	                }
// return the results of our query back to the iRule TCL
                var retString = JSON.stringify(results.records);
                return res.reply(retString.substring(1,retString.length-1));
            });
 
        }
        
    });
 
});
	 
//Start the ILX server
ilx.listen();

Create an irule named salesforce-query:

In our Workspace, we need to create an iRule. This is for convenience while working with iRules LX. You can reference iRules LX plugins and extensions from iRules edited in the normal iRule section of the interface as well.

 

Name our new iRule “salesforce-query” and click “OK”.  Then we use the editor window to paste the following code replacing anything that might already be there

# an iRule to grab the UserID, request a JWT authenticated session to Salesforce
# and return a query string
#
when HTTP_REQUEST {
#    # we need to grab the username from somewhere. TBD
#    # here it is set static for testing
 set user "my@email.com"
#    # Get a handle to the running extension instance to call into.
 set RPC_HANDLE [ILX::init sfDemo_pl querysf ]
#    # Make the call and store the response in $rpc_response
#    # we pass the userid to the js and get the query results back
 set AuthResponse [ILX::call $RPC_HANDLE getAuthorization $user]
# log local0. $AuthResponse
 HTTP::respond 200 content $AuthResponse Content-Type "application/json"
}

  

 

Step 3, use terminal to install node.js modules via npm

Use SSH to access the F5 terminal and use the commands below:

cd /var/ilx/workspaces/Common/sfDemo/extensions/querysf
npm install jsforce –install
npm install salesforce-jwt-bearer-token-flow –install

 

Step 4, create the director and server.key file used for signing our JWT

mkdir /var/ilx/workspaces/Common/sfDemo/extensions/querysf/keys
cd /var/ilx/workspaces/Common/sfDemo/extensions/querysf/keys
vi /var/ilx/workspaces/Common/sfDemo/extensions/querysf/server.key

Hit the “i” key for insert, <paste the pem formatted key in> and hit “:wq” to save and exit

 

Step 5, Create the iRules LX Plugin:

Go to iRules -> LX Plugins

Click Create:

And fill in the plugin properties:

Name: sfDemo_pl

From Workspace: sfDemo

 

Step 6, Create a Virtual Server for testing:

LTM -> Virtual Server

Create a simple HTTP VS and add the irule to resources:

 

 

Step 7, Validate we are in sync and perform a test query

Validate that your workspace shows to be in sync, (green arrow-circle):

With a browser, preferably in private viewing mode to avoid caching issues, go to our configured VIP, in this example http://10.1.10.29 and validate we get the expected values back for our Salesforce query.

You should see the results of the query in the browser.

This iRuleLX configuration can be customized to query whatever you want from Salesforce. This can include attributes for users that can be used to authorize APM sessions and access requests.

 

References:

https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5

https://jsforce.github.io/document/

https://www.npmjs.com/package/salesforce-jwt-bearer-token-flow

Credit:  some code was used from:

https://gist.github.com/jeffdonthemic/de5432f3e308882484f2acea68ebfabd

Updated Jan 31, 2023
Version 2.0