HowTo: Okta SAML at Citrix Gateway with SSO Without FAS

Introduction and Background

In one of my recent articles, I walk through a complicated configuration for Azure MFA via SAML at Citrix Gateway without the use of Citrix FAS. That particular configuration was quite complex, required Citrix ADC 13.0, and the Citrix ADC needs to stand in as an IDP during the authentication flow in order to capture credentials so we can re-use them during an LDAP factor.

Other identity solution providers have been hard at work devising their own workarounds to the authentication gap between SAML and Windows OS. In the case of Citrix deployments, such workarounds can be useful if an organization is constrained and unable to deploy Citrix FAS.

The common enabling component regardless of the solution is ensuring there is an LDAP factor for Citrix ADC to use for SSO to StoreFront and Citrix resources once successfully authenticated.

Although not publically documented by Okta (from what I have gathered in brief searching), Okta does actually have the means to allow SAML integration at Citrix Gateway and SSO to Citrix apps without the use of FAS. This is accomplished via an LDAP POST function which calls back to Okta to retrieve the user’s previously entered credentials in order to perform LDAP authentication at Citrix Gateway. Both SAML SP and LDAP authentication are used in this approach. This approach is not documented in Okta’s Citrix NetScaler Gateway SAML Configuration Guide which still acknowledges in its June 10th, 2019 iteration at least, that Citrix FAS is still needed for a full SSO experience using the setup they describe in that guide.

Okta does have an internal document that I believe they may make available to customers on request which outlines the LDAP POST solution mentioned earlier. A similar setup has been documented by Alchemy Technology Group’s article entitled “Integrating Okta with Citrix NetScaler Gateway without Citrix Federated Authentication Service“.

Both Alchemy and Okta’s documented solution uses basic authentication policies, which Citrix has deprecated and plans to discontinue in a future firmware release (presumably 13.1). Citrix wants customers to leverage advanced authentication policies (nFactor) for Citrix Gateway and AAA-TM fronted applications and this guide will illustrate how to accomplish that. Both aforementioned solutions rely on a responder policy bound to the Citrix Gateway to invoke the LDAP POST mechanism. This function retrieves the user’s previously entered credentials at the Okta login screen, and inject and auto-post them into an LDAP factor rendered by the Citrix ADC. Responder policies do not work effectively on AAA-TM servers unfortunately, so an alternative means to calling out to the Okta platform for the final authentication sequence is needed.

This article walks through the end-to-end setup of the Okta applications to support the solution, and how we configure nFactor constructs to successfully invoke the LDAP POST. Together this integration allows users to seamlessly SSO into StoreFront and Citrix apps and desktops without a second login prompt. By moving the authentication logic from basic policies to advanced policies, we also gain access to the highly extensible capabilities of nFactor which we otherwise cannot achieve via basic authentication policies.

Authentication Flow

For the purposes of this article, I have labbed out the solution with as the Citrix Gateway. The configuration used relies on sAMAccountName but with sufficient tweaking to Okta and LDAP policies on ADC, it can surely accommodate UPN, etc.

The authentication flow is as follows for our setup:

  1. User connects to
  2. Citrix ADC evaluates the first authentication policy bound to the authentication vServer in the authentication profile associated with the Citrix Gateway vServer.
  3. Citrix ADC sends a SAML request to Okta. In this initial sequence, the Citrix ADC is acting as a SAML Service Provider (SP) and Okta is acting as an Identity Provider (IdP).
  4. The user is redirected to the Okta login page and authenticates with their sAMAccountName.
  5. Okta applies appropriate access policies and MFA as specified in the Okta configuration.
  6. If MFA is successful, Okta sends a SAML assertion to Citrix ADC as a response to SAML Request #1.
  7. The authentication sequence proceeds to LDAP on the Citrix ADC, using a customized policy label which will retrieve credentials from Okta (captured in step #4) for the LDAP POST sequence.
  8. Citrix ADC evaluates LDAP credentials (using an LDAP server configured for sAMAccountName) such that they are the last credentials checked for SSO.
  9. User is authenticated to and passes through to StoreFront.


We do not rely on new features of more current Citrix ADC firmware in this configuration. As such the nFactor configuration for Okta should work on most current firmware (11.1, 12.0. 12.1, 13.0). I can confirm this configuration functions as expected on 13.0 (which happened to be the version of my current lab instance and plan to confirm it functions on 11.1 and 12.1 shortly.

The following assumptions are being made for our configuration:

  • An Okta tenant configured and integrated with your Active Directory environment.
  • Admin rights to the Okta tenant in order to provision applications.
  • Citrix ADC appliances built and configured, with any requisite firewall rules implemented.
  • Citrix Gateway vServer already built and integrated into a Citrix Virtual Apps & Desktops (CVAD) environment (StoreFront, Citrix Site).
  • A custom theme based on RfWebUI theme on Citrix ADC.
  • Functional Windows domain and a service account for LDAP.
  • TLS certificate (SAN, named, or wildcard) to cover one AAA-TM vServers and Citrix Gateway vServer.
  • Citrix ADC Advanced (formerly Enterprise) or above license.

Step 1 – Configure “Citrix NetScaler Gateway” Application in Okta

From within the Okta Admin console, navigate to “Applications” and search up “Citrix”. Select the “Citrix NetScaler Gateway” application.

Click “Add”.

Provide the application a useful label, and input the HTTPS URL for the Citrix Gateway portal. Do not include a trailing slash at the end of the URL. Click ‘next’ to proceed to configure SSO parameters.

On the SSO tab select “SAML 2.0” and define the application username format. This lab config is using sAMAccountName so we’re selecting “AD SAM account name”. Next, click the “View Setup Instructions” button which will open a new tab to walk through configuring the SAML SP configuration.

We’ll be grabbing various configurations dynamically generated on this screen. Scroll down to ‘Variables’ and capture each of the displayed parameters into a Notepad file, we’ll reference them for Step 4. Make a point to download the IDP’s public key certificate so we can upload to the ADC later.

Back on the SSO configuration page click ‘Done’ to complete the application configuration. Our Okta-side SAML configuration is now complete.

The new application will appear in the Okta admin application view. Click the application and navigate to ‘Assignments’. Assign the application to individual users or AD groups.

Step 2 – Configure an SWA “Template App” Okta

We need to add a second application in Okta, this time using Okta’s Secure Web Authentication (SWA) protocol. This will be used by the LDAP configuration we’ll be implementing in later steps on the ADC. Users being granted access to the Citrix Gateway via Okta will need to be added to both applications in order for the solution to function successfully.

From within the Okta Admin console, navigate to “Applications” and search up “Citrix”. Select the “Citrix NetScaler Gateway” application.

Click “Add”.

On the “General” tab, enter the following in the fields indicated below, but giving the application a useful name and replacing the FQDN of the “URL” with that of your Citrix Gateway, appending “/cgi/login” to the URL as per the example. These parameters will help Okta locate the input values on the login page the Citrix Gateway will render in a later “LDAP” factor so the user credentials can be auto-inserted and submitted for users.

Ensure the “Application visibility” and “Browser plugin auto-submit” checkboxes are also checked as outlined. We do not want users seeing this application as it is a secondary “hidden” authentication factor for users accessing Citrix Gateway.

Scroll down to locate the “App Embed Link” section of the page. Copy this URL, as we’ll need to reference it in Step 7. In the basic auth guides referenced earlier, this URL is used in a responder policy tied to the Citrix Gateway vServer. As that will not work with nFactor, we’ll use this URL in a “custom label” in nFactor to invoke the redirect at the appropriate time, for Citrix ADC to retrieve the credentials from Okta and inject it into the LDAP factor.

Click “Next” so we can configure the SSO parameters. On this screen select “Administrator sets username, password is the same as user’s Okta password”. Also, select the appropriate username format.

Important: This must match whatever the LDAP server being defined on Citrix ADC will be. In the case of this example, it’ll be sAMAccountName as our LDAP server configuration, so we’ll select “AD SAM account name” here.  If there is a mismatch in username format for what Okta is sending to what Citrix ADC is expecting to receive, it may result in authentication loops or errors. Click “Done” once complete.

Ensure you assign the same users or groups to this application, as you did the “Citrix NetScaler Gateway” application created earlier.

Step 3 – Create LDAP Server and Policy on Citrix ADC

In this example, an LDAP server using a “Server Logon Attribute value of sAMAccountName is being used, matching what Okta will send back per our previous configuration.

Create an advanced authentication policy of type LDAP, and select the previously created authentication server as the action. Use a value of “true” for the expression.

Step 4 – Create Okta SAML SP Server and Policy

Before proceeding, ensure the Okta certificate downloaded at the tail end of step 1 is uploaded to the ADC and added as a certificate. Then create a “SAML” type authentication action.

  • Redirect URL is the redirect URL as provided in the “Variables” section of the setup guide found in the SAML-based Citrix NetScaler Gateway application created in Okta earlier.
  • Bind the Signing Certificate provided by Okta application config (Variables section again) as “IDP Certificate Name”.
  • Although the config will default to NameID for the user field, enter it anyway.
  • Select the signing certificate which in this case will be the same TLS certificate you bound to the Citrix Gateway and its non-addressable vServer.
  • Input the Issuer Name parameters, which in the case of Okta will be a URL provides during the setup of the SAML application in the Okta porta. As with the other parameters, this was found in the “Variables” section.
  • Two-factor must be checked off, as this is needed in this dual authentication configuration.
  • Enforce Username should be checked for security (although it is unlikely sufficient time to override username password during the LDAP POST sequence occurs.
  • Implement the attributes as shown.

Step 5 – Built AAA-TM vServer and Auth Profile

Create a generic non-addressable AAA vServer as shown below and harden to org. standards. Use the server certificate of the Citrix Gateway on the AAA_GATEWAYOKTANOFAS vServer.

Then create the authentication profile as shown below, linking to our new AAA vServer.

Step 6 – Create Login Schema for LDAP POST

Create one Login Schema profile. This will be a custom XML file we’ll create based on the SingleAuth.xml built-in profile, which we’ll add another line item to in order to create a cookie that will activate our custom label.

Via WinSCP or similar tool, navigate to “/flash/nsconfig/loginschema/LoginSchema” on the Citrix ADC. Locate the SingleAuth.xml schema and duplicate it.

Edit this new policy and inject the following line of code on a new line before “</Requirements>”. This will create a new cookie.


The end result of the new login schema should look like this. Save and close. Login schemas will replicate between HA nodes as well.

<?xml version="1.0" encoding="UTF-8"?>
<AuthenticateResponse xmlns="">

Next, create the Login Schema profile, selecting the ldappost_singleauth.xml authentication schema we just created. Ensure “Enable Single Sign On Credentials” is checked off.  We will use this last schema’s credentials for SSO to StoreFront.

Step 7 – Create Custom Label for LDAP POST

The custom label function of nFactor I covered in an earlier article, and we’re using some of the same logic here as in our “redirect” reference from that post. It requires a custom RfWebUI-based theme and we’ll be editing the script.js file of that custom theme. This custom label will activate and redirect (standing in for the responder policy in the previously mentioned basic auth solutions) when triggered by the presence of the custom cookie created by our custom login schema. in the previous step.

In the example below, my custom RfWebUI-based them is called “Okta” thus its presence in the path. Using WinSCP or similar tool, navigate to “/var/netscaler/logon/themes/Okta” and edit the “script.js” file. Note our modifications will sync between HA nodes.

Edit the script.js file and enter the following code, close and save.

Important, the URL following “window.location.replace” in this example must be replaced with the embed URL we received from our LDAP POST “Template App” we created in an earlier step.

    // The name of the credential, must match the type returned by the server
    getCredentialTypeName: function () { return "ldappost_cookie"; },
    // Generate HTML for the custom credential
    getCredentialTypeMarkup: function (requirements) {
        var div = $("<div></div>");
        $(document).ready(function() {
          //Set cookie
          var exdays = 1;
          var d = new Date();
          d.setTime(d.getTime() + (exdays*24*60*60*1000));
          var expires = "expires="+ d.toUTCString();
          document.cookie = "NSC_NOCERT=TRUE;" + expires + ";path=/";
    // Simulate an HTTP redirect:
        return div;

Step 8 – Create LDAP POST Authentication Policy Label

In this step, we’ll link our custom login schema and our LDAP policy into a policy label. This will act as the “next factor” in our authentication low after the SAML SP factor.

Create the policy label and bind our login schema and okta_ldap_pol policy created earlier. No next factor is required.

Step 9 – Create nFactor Flow on AAA-TM vServer

Edit the properties of the non-addressable AAA vServer used by Citrix Gateway (AAA_GATEWAYOKTANOFAS). Bind the SAML SP policy created earlier by clicking “Authentication Policy”, and select the okta_ldappost_pl policy label as the next factor. Bind a “noschema” loginschema to the AAA vServer itself. Click “Done” to accept changes and get back to the previous screen.

Step 10 – Link Auth Profile to Citrix Gateway

The easiest step of all, binding the authentication profile we created earlier to the Citrix Gateway vServer. This authentication profile should link to the non-addressable AAA vServer for the Gateway.

Moment of Truth – Validation

At this point, all necessary configurations should be in place. Attempting to log into the Citrix Gateway should redirect users to Okta for authentication, and then back to Citrix ADC. For a split second the user may see a login screen, which will immediately disappear after the LDAP POST function activates and we’re passed through to StoreFront.

Accessing Gateway Page, Redirected to Okta

Successful Response from Okta, LDAP POST Invoked, SSO to StoreFront

Successful Launch of Citrix App – No Second Authentication Prompt

Citrix ADC Configuration

Below you will find all the necessary config params to build the configs mentioned above on the ADC. You’ll naturally want to adjust object names, certificate names, IPs.

## Certificate Certkeys and Linking ##
add ssl certKey okta -cert okta.cert
add ssl certKey wildcard_ferroque_dev -cert wildcard_ferroque_dev.pfx -key wildcard_ferroque_dev.pfx -inform PFX -passcrypt @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3
add ssl certKey SectigoINTER -cert SectigoINTER.cer -inform DER
add ssl certKey SectigoROOT -cert SectigoROOT.cer -inform DER
link ssl certKey wildcard_ferroque_dev SectigoINTER
link ssl certKey SectigoINTER SectigoROOT

## LDAP, SAML SP Actions ##
add authentication ldapAction LDAP_FERROSYS -serverIP -serverPort 636 -ldapBase "OU=your,DC=domain,DC=DEV" -ldapBindDn -ldapBindDnPassword @@@@@@@@@@@  -encrypted -encryptmethod ENCMTHD_3 -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName cn -secType SSL -passwdChange ENABLED -Attribute1 mail -Attribute2 objectGUID -Attribute9 sAMAccountName -Attribute10 userPrincipalName
add authentication samlAction okta_saml_sp_srv -samlIdPCertName okta -samlSigningCertName wildcard_ferroque_dev -samlRedirectUrl "" -samlUserField "Name ID" -samlIssuerName "" -samlTwoFactor ON -Attribute1 mail -Attribute2 userPrincipalName -logoutURL ""
add authentication authnProfile AAA_GATEWAYOKTANOFAS_authprof -authnVsName AAA_GATEWAYOKTANOFAS

## Login Schemas, Policies, Policy Labels ##
add authentication loginSchema okta_ldappost_lschema -authenticationSchema "/nsconfig/loginschema/LoginSchema/ldappost_singleauth.xml" -SSOCredentials YES
add authentication loginSchemaPolicy noschema -rule true -action LSCHEMA_INT
add authentication Policy okta_saml_sp_pol -rule true -action okta_saml_sp_srv
add authentication Policy okta_ldap_pol -rule true -action LDAP_FERROSYS
add authentication policylabel okta_ldappost_pl -loginSchema okta_ldappost_lschema
bind authentication policylabel okta_ldappost_pl -policyName okta_ldap_pol -priority 100 -gotoPriorityExpression NEXT

## AAA-TM Servers ##
add authentication vserver AAA_GATEWAYOKTANOFAS SSL -maxLoginAttempts 10 -failedLoginTimeout 15
set ssl vserver AAA_GATEWAYOKTANOFAS -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000
bind authentication vserver AAA_GATEWAYOKTANOFAS -portaltheme Okta
bind authentication vserver AAA_GATEWAYOKTANOFAS -policy noschema -priority 100 -gotoPriorityExpression END
bind authentication vserver AAA_GATEWAYOKTANOFAS -policy okta_saml_sp_pol -priority 100 -nextFactor okta_ldappost_pl -gotoPriorityExpression NEXT
bind ssl vserver AAA_GATEWAYOKTANOFAS -certkeyName wildcard_ferroque_dev

## Gateway Server ##
add vpn vserver SSL 443 -downStateFlush DISABLED -Listenpolicy NONE -tcpProfileName nstcp_default_XA_XD_profile -maxLoginAttempts 10 -failedLoginTimeout 15 -authnProfile AAA_GATEWAYOKTANOFAS_authprof
add vpn sessionAction BASELINE_SESSION_WEB_PROF -sessTimeout 60 -clientSecurityLog ON -splitTunnel OFF -transparentInterception ON -defaultAuthorizationAction ALLOW -ssoCredential PRIMARY -icaProxy ON -wihome "" -ntDomain -clientlessVpnMode OFF
bind vpn vserver -staServer ""
bind vpn vserver -staServer ""
set ssl vserver -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000
bind vpn vserver -portaltheme Okta
bind vpn vserver -policy BASELINE_SESSION_WEB_POL -priority 100 -gotoPriorityExpression NEXT -type REQUEST
bind ssl vserver -certkeyName wildcard_ferroque_dev


Troubleshooting Tools

As this is an elaborate configuration, there are many opportunities for things to go wrong. Having three PuTTy sessions open with the following commands at the ready are quite useful, all executed from shell. Note that it may be worth enabling DEBUG logging on the global internal Syslog configuration of the appliance while troubleshooting, and disabling it afterward. System > Auditing > Settings > Change Auditing Syslog Settings > Log Levels = ALL.

Examining authentication logs:

cat /tmp/aaad.debug

Examining Syslog events during authentication (note you can append ‘| grep -i xxx’ where xxx us a key term such as SAML or AAA to filter on:

tail -f /var/log/ns.log

Examining policy hits on nFactor configuration:

nsconmsg -d current  -g pcb_hits

The SAML-Tracer extension for Chrome or Firefox is also an invaluable tool for debugging SAML authentication.

The most common issue one might encounter setting up this configuration is constant looping of authentication flow, even without the LDAP POST configuration in the picture. If the LDAP POST configuration is not in place and just SAML authentication is looping between Citrix ADC and Okta, double-check your Okta Citrix Gateway application configuration (in the Okta portal) and ensure there is no trailing slash on the “Login URL”. If SAML works fine standalone but you experience looping with the LDAP POST aspect, Okta suggests this might be caused by a mismatch between the username format configured in Okta and on the Citrix platform which might need adjustment in your Okta application config or on the LDAP server on Citrix ADC (and potentially session policy and StoreFront trusted domain config).

During testing and troubleshooting, I strongly recommend clearing the user’s sessions from Okta between tests, and either testing in private browser sessions or clearing the website’s cookies between tests as well.

0 0 vote
Article Rating
Notify of
Inline Feedbacks
View all comments
3 months ago

Hi Michael,

Would this work if the AD was not on-premise (NetScaler side of things) instead it was an Azure AD (linking back to another businesses Domain) and through OKTA end users consume the Citrix apps in NetScaler side of things?

Like a B2B scenario, where B1 provides the AD+Azure AD+OKTA and wish their users to connect to B2 which has NS/SF/XenApps (No AD!)


16 days ago

Hi Michael, just out of curiosity, how would that work if you also have apps, mail and file services in the B2 domain in your XenApp sesions? Was the BYO-IdP in the end linked to an AD in the B2 domain? I might face a similar requirement … Cheers!

3 months ago

Thank you for publishing this guide. Do you know if this method of integration will publish an OKTA tile for our users to access from their OKTA page? The best user centric benefit OKTA provides is having the users apps located in one convenient location.

Also, what steps could be taken to allow for administrators to test the service before releasing to production?

16 days ago
Reply to  Silvestre

Hi Silvestre, this will work, at least, for me it does ..

abdullah a mamun
abdullah a mamun
3 months ago

Hi Michael,

Well documented article. I am testing this setup and have it all setup as mentioned on your doc. I am getting malformed assertion error after the login. I do see the login screen for a split second then get the malformed error. I know the SAML policy works because if I set my Storefront as delegated to ADC I get through and the second windows login prompt and can access my apps.

abdullah a mamun
abdullah a mamun
3 months ago

Yeah I thought that as well but the same SAML policy works if I change my storefront to delegate the Auth to ADC.

Are you thinking the mismatch is on the attributes ?

I am on v12.1.57.18 on the ADC. Have you tested on the v12.1 ?

Prabhu Govindaraj
Prabhu Govindaraj
2 months ago

Hi Mike,
Firstly thanks for the amazing article. When I thought it is end of world with out having FAS I stumbled on your blog. You are a lifesaver. Coming down to my question I have implemented the exact setup and I receive error after redirecting to logon page for split second. “Matching policy not found while trying to process Assertion; Please contact your administrator” . I am running netscaler version 13.0 build 58.32nc. I am wondering if there is any additional settings that we need to make in storefront for this configuration?

18 days ago

Hi People, very fine article and I would be very happy without needing the FAS component.. I am quite sure I followed every bit of this article, but on the moment of truth I am redirected to Okta, and after filling in the right creds I get a ‘SAML Assertion verification failed; Please contact your administrator’ .. any ideas what might be causing this?

16 days ago

Hi Michael, it was a pile of things after all. I assigned the wrong cert in my SAML server, did not select the right portal theme in my gateway vserver (forgot to switch to the cloned/adjusted one) and last but not least, from NS 13 onwards you need to assign a traffic policy to your vserver with as only option SSO turned on. Stuff works flawlessly now =) Very good article and funny way of using the script.js for this! Tx for publishing!

Would love your thoughts, please comment.x