Implementing CFML SSO (SAML)

I am trying to implement SAML in one of our Lucee/CFML applications against an Okta SSO server. I downloaded the Demo App from @psarin here: GitHub - psarin/cfml-sso-demo-app: Demo app for cfml - sso integration, using the Okta identity provider as an example.

It says that once I configure it via the admin site, I can copy the /config, /certs, and /saml to my application. But when I do that, my default index.cfm comes up. I don’t know how to get the SAML redirection to come up.

So, basically, how do I use this library?

Thanks,
Harry

Don’t forget to tell us about your stack!

OS: Red Hat Linux 8.10
Java Version: 21.0.6
Tomcat Version: Unknown
Lucee Version: 6.0.1.83

Not sure if it’s the same issue, but here’s a link to the issue I had:

Once the config directory was created, it worked as expected.

1 Like

I have the directory there. What I did was configured everything as per the Readme under /cfml-sso-demo-app, then copied the /saml, /config/ and /certs folders over to my app. So the okta.json is under /config, and I created an okta.crt under /certs with the cert given to me by our Okta team. In the okta.json file, I see the consumeUrl is “https:///saml”. Do I need more to that URL? I could post the okta.json but I’d probably have to redact some of it.

Thanks,
Harry

I’m not sure TBH. If you want to PM me your email, I’ll zip the site and send to you. Then you can compare your code with a working version.

Yes, the consumeUrl should point to the URL you want the IdP to redirect to (e.g., https://yourdomainname/login) after successful auth by them. The issuerUrl should list the IdP URL that will perform the auth for you (e.g., https://XXX.okta.com/app/someappname).

2 Likes

But that’s where my confusion sets in. I know the issuerUrl is right because our Okta team sent us that. Right now, my consumeUrl is the path to our application (https://mydomain/appname). In the index.cfm for my application, I have the following:

if (cgi.request_method == “POST”){

request.identityProviderModel.setCompanyName(form.companyName);

request.identityProviderModel.setConsumeUrl(form.consumeUrl);

request.identityProviderModel.setIssuerUrl(form.issuerUrl);

request.identityProviderModel.setIssuerID(form.issuerID);

request.identityProviderModel.setCertificate(form.certificate);

data = serializeJson(request.identityProviderModel)

fileWrite(“config/” & request.identityProvider & “.json”, data)

session.saved = true;

sleep(250);

location(cgi.http_referer,false);

}

I’m not 100% sure that’s correct, but irregardless, I have NO redirection to the Okta login happening at all, and I’m not sure why.

Thanks,

Harry

The above is only to generate the config file and need only be done once.

The consumeUrl will be a page that consists of your own code and that will handle what Okta sends you. The info that Okta will POST includes the encodedSAMLResponse , which you need to decode using the functions provided in the SAML library.

Here is some sample code which you’ll have to modify / adapt.

var samlResponse = null;

var identityProvider = 'okta';
var identityProviderModel = new PATHTO.saml.providers.okta(argumentCollection = YOURLOADEDCONFIGFILEASSTRUCT);

if (!isNull(arguments.encodedSAMLResponse)) {
            var samlHelper = new PATHTO.saml.saml(argumentCollection = {
                idProvider: identityProvider,
                idProviderModel: identityProviderModel
             });

samlResponse = samlHelper.buildPacket(arguments.encodedSAMLResponse);

          /**
           * samlResponse = 
           * {
           * verifications: {
           * 	signature: isValidSignature,
           * 	conditions: areConditionsValid
           * },
           * issuer: issuer,
           * subject: subject,
           * conditions: conditions,
           * response: unencodedSamlResponse
           * }
           */
}

So I’m REALLY not great with CFM files. If my consumerUrl is https://domain/SATTS/saml, does that mean that I need to make an index.cfm in the consumerUrl folder, and put your code example? Obviously I’d have to change the PATHTO and other values from your example. But that still, to me, doesn’t have a user going to our application, and getting redirected to it.

Now, I will say this: the current application (which I didn’t write or maintain, we just host it; nobody maintains it so I’m now “it”), would you recommend that I have a brand new index.cfm that has the user click a button or link to login, and have that go to the issuerID Okta site?

Once that’s all figured out and working, I may have a question about how this application handles logins. Currently, the site comes up and shows the user everything, and there is a crude “login” mechanism that brings up a dialog, where the user enters their email address and a password. I know that with Okta I can get the email address returned, but I don’t know how to “look up” that user in the database.

Thanks for any help! I have gotten the Okta MFA working with some in-house PHP code using OneLogin/PHP-SAML, Apache Guacamole using their SSO plugin, Gitlab CE, and Redmine with their SAML plugin. So I understand the concept, and actually the cycle of all of this, but my low knowledge of CFM is holding me back here.

Thanks,

Harry

Somebody else has been also helping me, but we can’t get past this particular issue. I have the following in index.cfm:

<cfif !isNull(request.identityProvider)>

<cfset postURL = ‘/’ & request.rootDir & ‘/post.cfm’ />

PostURL = “#postURL#”
SiteURL = “#request.siteURL#”

SAML Login

Click below to login using #provider#.

PROCESSING

The variables such as #postURL#, #request.identityProvider#, and #request.siteURL# aren’t being evaluated. Other areas of that file, which has worked for years, have variables such as #nFirstName#, etc. that evaluate properly. I have those set in Appication.cfc, and from what I’ve seen, Application.cfc gets run before the index.cfm is executed, so why would those not be evaluated for use at that time?

As a debug operation, I tried to print those variable, and if you look at the form above, where the image is loaded for the button/link, the image won’t render. And if I clicked on the placeholder icon there, I would expect post.cfm to get called, but it doesn’t. The index.cfm gets reloaded. So I’m COMPLETELY lost here and could really use someone’s help.

Thanks,

Harry

your images did not post

My suggestion is your “/” could be an issue.

try something like

<cfset postUrlPart1 = request.rootDir  >
<cfset postUrlCombined ="/#posturlpart1#/post.cfm>
<cfoutput>

Your full code for this page would be better for everyone to take a look at and give you suggestions.

Yes, recommend setting up a workable example on some code share site and sharing it.
Please clarify your goals and what you expect the login workflow to be. I assume some unauthenticated user visits your app, you redirect to Okta, then Okta redirects back to you after login, you decode and store credentials as needed. Therefore, you need to write your own code to redirect to Okta, with help from utilities in library, and your own code to handle decoding and credential storage, with partial help from utilities in library. I doubt you’d want to do this in an “index.cfm” but rather via CFM or CFC in some dedicated login folder.

Here’s a redacted version of my index.cfm. I took out a bunch of content near the bottom between the

as it really wasn’t important. I hope it helps.

Thanks,

Harry

textarea{ font-family:Arial, Helvetica, sans-serif;}

<cfif !isNull(request.identityProvider)>

PostURLPart1 = “#postURLPart1#”
PostURLPart2 = “#postURLPart2#”

SAML Login

Click below to login using #provider#.

PROCESSING

Your images are not posting.

first before you muck with any further form fields just create variables and assign them the values you need to preform action you want.

You may make sure you have completed all the steps outlined by okta

Well, let me preface this by saying that the site I’m working on was not written by me. No excuse, but I’m doing my best to navigate my way around how CFM works. With that being said, I do have some familiarity on how SAML with Okta works. Our group has implemented SAML with Okta on several Apache Guacamole servers, 2 Gitlab servers, 3 Redmine servers, and 4 “in-house developed” PHP sites using OneLogin/PHP-SAML.

I configured the okta.json file using the application in my initial post. I copied it over to my application. So I have my app folder, then under that I have /config, /certs, /includes, and /saml. The Okta information was provided to me by our Okta team. I don’t have access to the settings. The /config/okta.json is similar to this:

{“companyName”:“redacted”,“consumeUrl”:https://mydomain.org/consume.cfm,“issuerUrl”:https://mydomain.org/saml,“issuerID”:http://www.okta.com/redacted/sso/saml,“certificate”:“-----BEGIN CERTIFICATE-----redacted-----END CERTIFICATE-----”,“createdOn”:“Wed, 26 Feb 2025 13:09:19 GMT”

}

Another user who was helping me sent me their code and I tried to implement some of it. They gave me an Application.cfc that I tried to adjust for my environment:

/**

  • @output false

*/

component{

this.name = “SATTS”;

this.sessionmanagement = true;

this.sessiontimeout = createTimeSpan(0,7,0,0);

this.triggerDataMember=true;

this.invokeImplicitAccessor=true;

// Mappings

this.mappings[‘/saml’] = getDirectoryFromPath(getCurrentTemplatePath()) & ‘/saml’

// java settings

this.javaSettings = {

LoadPaths : [“/saml/jars”],

reloadOnChange : true,

watchInterval : 60

};

public boolean function onApplicationStart() {

application.datasource=“FAA Okta”;

return true;

}

public boolean function onRequestStart(){

request.identityProvider = “okta”;

request.rootDir = “SATTS”

request.siteURL = “http” & (cgi.server_port_secure ? “s” : “”) & “:” & “//” & cgi.server_name & “:” & cgi.server_port & “/” & request.rootDir;

if (structKeyExists(url,“reload”)){

location(“./”,false);

}

// check if identityProvider is setup and configured

if (!isNull(request.identityProvider)){

try{

data = deserializeJson(fileRead(‘config/#request.identityProvider#.json’));

request.identityProviderModel = createObject(‘component’, ‘saml.providers.’ & request.identityProvider).init(argumentCollection = data)

}catch (any e){

request.identityProviderModel = createObject(‘component’, ‘saml.providers.’ & request.identityProvider).init()

}

if (isNull(request.identityProviderModel)){

request.identityProviderModel = createObject(‘component’, ‘saml.providers.’ & request.identityProvider).init()

}

// relocate to admin if not completely setup

if (!findNoCase(“admin”,cgi.script_name) && !request.identityProviderModel.isReady())

location(“/#request.rootDir#/admin.cfm”,false);

}

return true;

}

}

There’s also a post.cfm:

try{

isssuerFinalUrl = request.identityProviderModel.getRedirectToIdentityProviderUrl()

// now send to identity provider

location ( isssuerFinalUrl, false);

}

catch(Any e){

writeDump(e);

}

And a consumer.cfm but it’s really geared towards their environment.

So in the spirit of “starting from scratch”, should the flow be something like this? And the first basic question on this is what’s the default entry point)?:

→ /post.cfm → Okta → consume.cfm → index.cfm?

Thanks!

Harry

I’m just getting back to this. I have my Application.cfc, I believe, working so it should go to /myapp/post.cfm, but I can’t verify that right now 100%. The okta.json file under /myapp/config shows https://mydomain/myapp/consume.cfm as the consumeUrl and https://mydomain/myapp/saml for issuerUrl. I also have a stripped down version of consume.cfm than last week, but when I go to the site, I’m immediately taken to index.cfm and the SAML stuff is completely skipped over. THAT’S what I need help on at the moment. I can’t even get the redirect to SAML then back to work here, and I know how its supposed to work as I’ve gotten it working on over a dozen other systems, but none of those are CFM or Lucee. This is my first one.

Thanks,

Harry

You say issuerUrl is https://mydomain/myapp/saml but this needs to be the okta endpoint, not your own web app. Unless you are running an IdentityProvider at mydomain, in addition to your app. Please see my previous reply Implementing CFML SSO (SAML) - #5 by psarin . What do your other systems say these two URLs should be?