Introduction and Background
NOTE: As of June 8th, 2021, Citrix has identified two vulnerabilities covered in CTX297155. Upon remediation with appropriate firmware, SAML configurations require adjustment as per CTX316577. This article’s examples do not contain those adjustments and readers are encouraged to modify their deployments accordingly to mitigate the security risk.
As an introductory disclaimer, I alone did not devise this solution, but merely completed its development in its latest iteration. Much of the legwork was developed by an expert team of Citrix Consulting and Citrix ADC Engineering professionals over several iterations for a customer with unique constraints, which prevented them from deploying Citrix Federated Authentication Service (FAS).
Update April 27th 2020, I’ve released a similar article on Okta SAML auth at Citrix Gateway without FAS, but using Okta’s LDAP POST via SWA functions instead of an ADC-hosted IDP.
I should note before we get too far along that this alternative solution has a hard requirement of Citrix ADC standing in as an on-prem IDP (in lieu of ADFS for example), and is therefore not universally suitable for all customers. With that said, this requirement is easy enough to work around by using another custom domain with the Azure AD tenant and federating that domain with the ADC-hosted IdP. The “Login URL” string we would set in the SAML SP configuration on the ADC would be appended with the following after /saml2 “?whr=customDomain” i.e. …/saml2?whr=customDomain where customDomain matches the custom domain added to the Azure AD tenant and federated to the ADC-hosted IdP. We are in effect “directing” Azure AD to use a specific WS-Federation realm we have set up, and leave the default configuration of Azure AD to continue authentication users as normal, and not impacting non-Citrix SAML authentication flows, be they federated elsewhere (ADFS, another IdP, etc.) or not.
Furthermore, this guide should work for other SAML solutions beyond Azure AD, but I have yet to test-drive this. I have been advised that this method has seen use outside of Citrix altogether, to allow conditional access and SAML to front applications that cannot support SAML natively. Cool stuff.
Starting off with a little bit of background, customers seeking to leverage SAML authentication at Citrix Gateway by default tend to need some additional Citrix components. Citrix FAS enables users to authenticate via SAML in order to maintain a single sign-on (SSO) experience to their Citrix resources once logged in, just as when using LDAP.
Why do we need FAS? Because SAML isn’t natively spoken by Windows. So if it’s your only authentication method at Citrix Gateway (i.e you’re not using it in combo with LDAP) you’re not going to log into Citrix Gateway and SSO into your Citrix resources. A user authenticating via SAML at Citrix Gateway would be passed through to Citrix StoreFront but would get a second Windows login prompt when launching the app or desktop in absence of FAS. If you’re interested in Citrix FAS (which remains the “lead with” strategy for SAML auth to Citrix resources), I suggest checking out Carl Stalhood’s FAS article, which is as always, a thorough walkthrough.
Side note The Beer Drinker’s Guide to SAML provides a hilarious and layman’s term overview of SAML for those who do not yet fully grasp its constructs or how it works. Highly recommended.
SAML not providing SSO out of the box with Citrix isn’t a Citrix issue, it’s a Windows limitation. That’s why FAS was conceived. It bridges the gap between SAML and Windows-native authentication methods. In the case of FAS, it’s using certificates as that mechanism (or more specifically virtual smart cards).
For some customers, FAS may not be possible due to various direct or indirect reasons. In the case of the particular customer this solution was developed for, they had the following challenges for their SAML solution of choice (Azure AD \ Azure MFA):
- Due to corporate policy, cannot sync passwords nor password hashes into Azure AD.
- Due to an Active Directory limitation in their environment, unable to leverage ADFS as an IDP for Azure AD to interface with, which could help overcome the prior point.
- Not able to use Microsoft Network Policy Server (NPS) with the Azure MFA extension. This legacy mode does not allow for conditional access policies which is a non-starter for some customers.
On account of the first two points, a solution was devised using a Citrix ADC-hosted IDP AAA-TM vServer to stand in for ADFS, and federating Azure AD with this domain using the IDP. With this in place, we have means to capture the user’s LDAP credentials when authenticating to the IDP and replay them during a later authentication sequence in an LDAP policy, thus achieving SSO to Citrix resources. We achieve this store and retrieval logic by using the Citrix ADC variables and assignment function which stores data in memory (encrypted if specified) for later retrieval.
The purpose of this article is to walk through the setup of this solution. ADC CLI commands to build out the configuration (swapping in one’s own IPs, certs, names, etc.) are included at the end of the article.
For the purposes of this article, I have labbed out the solution with gateway.ferroque.dev as the Citrix Gateway, and idp.ferroque.dev as the Citrix ADC-hosted IDP (AAA-TM vServer). The authentication flow is as so:
- User connects to https://gateway.ferroque.dev.
- Citrix ADC evaluates the first authentication policy bound to the authentication vServer in the authentication profile associated to the Citrix Gateway vServer.
- Citrix ADC sends a SAML request to Azure AD (SAML Request # 1). In this initial sequence, the Citrix ADC is acting as a SAML Service Provider (SP) and Azure AD is acting as an Identity Provider (IdP).
- Azure AD upon receiving SAML Request # 1 sends a new SAML request to Citrix ADC. (SAML Request # 2). Here, Azure AD is acting as a SAML SP and Citrix ADC is acting as an IDP.
- Azure AD redirects the user to https://idp.ferroque.dev as per the federation configurations for the domain, and is prompted for AD credentials (sAMAccountName format in this scenario, but could accommodate for UPN as well). Citrix ADC validates LDAP credentials.
- Citrix ADC invokes a global variable and assignment configuration to store the user credentials for up to 1 hour before expiring them. These credentials are stored in a map in memory and are encrypted so they remain obfuscated in event for a core dump.
- Upon successful authentication, Citrix ADC evaluates SAML IdP policy.
- Citrix ADC sends a SAML response or assertion to Azure AD (Response to SAML Request #2).
- Azure AD applies conditional access policies, multi-factor authentication, etc.
- If MFA is successful, Azure AD sends a SAML assertion to Citrix ADC as a (Response to SAML Request #1).
- Citrix ADC evaluates LDAP credentials (using a second LDAP server using UPN) such that they are the last credentials checked for SSO, using a login schema configured to extract the previously stored password from step #6.
- User is authenticated to https://gateway.ferroque.dev and passes through to StoreFront.
A high-level flow looks like this:
LDAP Auth –> Store Credentials –> MFA –> Retrieve Credentials –> SSO to Citrix Gateway (and subsequently to StoreFront and Citrix apps)
As you might have guessed, this solution will use nFactor in Citrix ADC on the AAA vServers. The diagram below outlines the various authentication components involved in this solution which will be built out later in this guide:
For the purposes of this article, the following assumptions are being made:
- A Citrix ADC 13.0 and Citrix Gateway vServer already built and integrated into a Citrix Virtual Apps & Desktops (CVAD) environment (StoreFront, Citrix Site).
- Functional Windows domain and a service account for LDAP.
- A domain not yet enabled for federation added to Azure AD (custom domain) and has been ‘verified’.
- The ability to have Citrix ADC act as an IDP for the user domain (i.e. another solution such as ADFS is not already doing this for the domain).
- TLS certificate (SAN, named, or wildcard) to cover two AAA-TM vServers, Citrix Gateway vServer.
- Public key certificate for the IDP AAA-TM vServer for use in IDP federation process between Azure AD and Azure MFA
- Sufficient rights in Azure AD to federate a domain.
- An IP for the IDP AAA-TM.
- An Enterprise Application configured for SAML authentication for use by our Citrix Gateway.
- Citrix ADC Advanced (formerly Enterprise) or above license.
Step 1 – Create Variables and Assignments
Variables are a powerful AppExpert function on Citrix ADC which allows the storing of data within memory for a period of time and can be called upon by referencing an assignment corresponding to the variable. In our use case, we’re building a variable map for username and password, captured from the initial LDAP authentication the user experiences at the Citrix-owned IDP. Using variables allows us to call on credentials for re-use between authentication sessions. For more information on variables, please reference Configuring and Using Variables.
We’ll first start by creating the variable itself. The following screens detail the key inputs needed. The values will be stored in appliance memory so be mindful of this when sizing inputs. In the below example, we have a key and value length totalling 512 bytes, and up to 1,000 entries permitted within a 1 hour period max. As we’ll be encrypting and there will surely be some metadata and overhead, multiplying by 1.1x should give us enough of a buffer. 512 x 1000 x 1.1 = 563,200 bytes or 0.5 MB.
We’ll now create the assignment to pair with the variable. The variable shown below will be what we use to call upon the key data for credential replay later. We are encrypting the stored credentials within appliance memory to obfuscate the passwords should a core dump need to be sent to a third party for analysis (Citrix Support, etc.). The key expression will use the sAMAccountName the user enters into the first LDAP prompt at the ADC-owned IDP.
Step 2 – Create LDAP Servers
This solution uses two different LDAP servers for two different phases of the authentication sequence. The customer this was developed for wanted to permit users to log in with UPN or sAMAccountName. For the purposes of this article, we’re sticking with sAMAccoutName for simplicity. Adding UPN as a secondary authentication server for initial login is not a significant ordeal, however.
The second LDAP server we call the “SSO” server. Assuming we’re getting a NameID\UPN from the Azure AD to Citrix Gateway AAA vServer in the second half of the auth sequence we use an LDAP server configured with the Server Logon Attribute of userPrincipalName to correctly look up and authenticate the user. This will also be the credential pair passed over to StoreFront.
Step 3 – Create SAML IDP Profile, SAML SP Server, and IDP Policies
We’ll now build the SAML IDP configuration the ADC will use. We will bind this to the appropriate IDP AAA-TM vServer in the subsequent step.
- Assertion Consumer Service Url will always be for Azure: https://login.microsoftonline.com/login.srf
- Service Provider Logout URL will be FQDN of your IDP followed by cgi/tmlogout
- For the IDP Certificate Name, bind the IDP certificate (i.e. one you have a private key for, the same one you will bind to your ADC-owned IDP AAA vServer. This is NOT the Azure IDP signing certificate!).
- Audience: Will always be: urn:federation:MicrosoftOnline
- Leave skew time default at 5 minutes
- Set Name ID Expression to: AAA.USER.ATTRIBUTE(2).B64ENCODE
- Set Sign Assertion to Assertion
- Change Signature Algorithm to RSA-SHA256, SHA256
- Define the Attribute 1 values as shown below
We’ll then create a SAML IDP policy linking to our newly created IDP profile. The expression will look explicitly the Microsoft Online URL:
If not already completed, go ahead and build out the Azure AD enterprise application configuration as one would if federating Citrix ADC with FAS. This article will not go through those details, plenty others do, but here is a screenshot of the lab config. Be sure to assign users. Be sure to configure the SAML SP server to use the certificate downloaded from Azure for the IDP certificate (not the certificate of the ADC-owned IDP).
Now we move on to create the SAML SP profile which the Citrix Gateway’s AAA vServer will use as the first authentication factor. This is where the enterprise app details created previously will be used. The Azure enterprise app IDP certificate should be downloaded and installed on the ADC. It may show up under the “Unknown” certificate store once installed.
- Redirect URL is the Azure enterprise application’s “Login URL” provided at the time of app creation. Append ?whr=yourdomain.com after /saml2 if you have more than one domain and need to auto-select the domain for the user to reduce login steps such as hitting Azure, being asked for email, then being redirected to IDP and asked to enter email again plus password.
- Bind the Signing Certificate provided by the Azure enterprise application config 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 and Audience parameters, which should be the URL of the Citrix Gateway users are logging into.
- Tune the Signature Algorithms as shown.
- Check off Force Authentication.
- Implement the attributes as shown.
With the SAML SP server out of the way, we’ll create the advanced authentication SAML SP policy linking to the server of type “SAML”. In this example, we’re calling it saml_sp_policy_to_aad_idp. The expression to be used is “HTTP.REQ.COOKIE.NAME_VALUE(“NSC_TASS”).CONTAINS(“idp.ferroque.dev”).NOT” and the policy will link to SAML_MSFT_SRV as the action.
Step 4 – Building AAA-TM vServers and Auth Profiles
The solution requires two AAA-TM vServers. The first one is a non-addressable AAA vServer which will be linked to the Citrix Gateway vServer. This AAA flow uses Azure AD SAML policy, followed by an LDAP factor (second LDAP aka SSO LDAP) that seamlessly passes through credentials stored in a variable.
The second AAA vServer needs to have an IP address and be reachable by users. It performs the role of SAML IDP, as well as the first LDAP factor. Post-LDAP authentication, the last, invisible factor stores the credentials in a variable value.
Build out two generic AAA vServers as shown below and harden to org. standards. Use the server certificate of the Citrix Gateway on the AAA_GATEWAYNOFAS vServer, and use an appropriate server certificate on the AAA_IDP vServer. Note the public key of the AAA_IDP’s certificate will be needed to create the Azure AD federation task in step 10.
Then create the authentication profile.
Step 5 – Create Login Schemas
Create two Login Schema profiles. Both will use a noschema schema. Both are used on LDAP factors, and one is used to store the LDAP credentials from the initial LDAP authentication (sAMAccountName in our example) and the second one for retrieval of those credentials and used in conjunction with the second LDAP authentication (for SSO using UPN).
StoreCreds_LS does not require any user or password expression entire and could just as easily be substituted for built-in LSCHEMA_INT.
The “PreFillUsernamePassword_LS” however, is our value retrieval schema and does require additional configuration in order to grab the previously stored password. This expression in the password field looks up the username in the variable map. The string looks for the username before the @ symbol, as Azure AD will be sending back UPN. It is assumed that the username portion of UPN matches that of sAMAccountName. If it does not in your organization, modifications to SAML and LDAP server properties for user attributes may be needed. The expression also decrypts the stored password for use.
Step 6 – Create Authentication Policies
Two authentication policies (for our two LDAP factors) are needed. They will respectively link with the previously created LDAP servers. In both cases, the expression of ‘true’ is sufficient.
We also need to create the policies for our store credential procedures when authenticating at the ADC-owned IDP. Note that “store_creds_policy” cannot be created in the GUI as of ADC 13.0 b55.24. The command has to be run from CLI to create an authentication policy that can reference a variable assignment (which will thus store the credentials as configured earlier). This command only seems to work on 13.0, in testing on 12.1 b56.22 it crashes the appliance forcing a reboot. Have not tried on earlier builds of 12.1 as we require 13.0 regardless.
add authentication Policy store_creds_policy -rule true -action idp_usercreds_assign
Step 7 – Create Authentication Policy Labels
We must create two policy labels to accommodate for “second factors” on the respective AAA vServers.
For the Citrix Gateway’s corresponding vServer, the first factor is Azure MFA, followed by the auto-filled credential LDAP (SSO UPN) authentication as a second factor which we’ll configure on a policy label in order to set the right login schema.
For the IDP’s vServer, the first factor is LDAP (SAM) followed by a policy label with an initial policy to store the username and password credentials and a second policy that passes through and gives a success state as no “success state” response consumable by nFactor when calling the assignment.
Step 8 – Create nFactor Flows on AAA-TM vServers
Edit the properties of the non-addressable AAA vServer used by Citrix Gateway (AAA_GATEWAYNOFAS). Bind the SAML SP policy created earlier by clicking “Authentication Policy”, and select the PreFillUsernamePassword_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.
Edit the properties of the AAA_IDP vServer (the one with the routable IP) and we will bind two policies here; SAML IDP and LDAP.
For LDAP, click “Authentication Policy” and bind the sAMAccountName LDAP policy and select the next factor as the Assign_StoreCreds_PL policy label.
Next, let’s bind the SAML IDP policy. Note that if you make changes to the SAML IDP policy expression after binding to the vServer, you may need to unbind and re-bind in order for it to take effect.
Step 9 – 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.
Step 10 – Federate Domain with Azure AD using Citrix ADC IDP
We’ll now create our federation in Azure AD for the domain, in our case ferroque.dev which was previously added and validated in Azure AD (as a prerequisite) under the Azure > Custom domain name section of the Azure portal. Once the commands are invoked, note that it can take 15 minutes or so for the changes to replicate.
Note: If Azure AD SAML authentication is already in use, it is important this be the last step as you’ll effectively be changing the way users authenticate to Azure AD for their SaaS apps at this point. Prior to this, authenticating directly at the IDP to verify no errors are encountered is important (ignore the “Target URL not found for redirect after successful login.” message can be ignored in such a test).
First, from an administrative prompt on a Windows system, run the following commands to install and log into the Azure PowerShell cmdlets.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Install-Module -Name MSOnline
You should get an output like this:
Next, run the following command to authenticate to Azure AD.
Log in with the global admin account or another account with sufficient rights to conduct domain federation.
For the next step you’ll want to grab the public key from the certificate you’ll be using to secure the IDP. In my case I was using a wildcard certificate for all the vServers in the lab and just exported the certificate without private key in DER format. In the example below, the federation commands will look for the file in C:\. This is needed for Azure AD to trust the IDP’s assertions.
The following variables and commands will invoke the federation of Azure AD to the domain which in this example is ferroque.dev. Note the URIs used in this configuration, which should match URIs used previously in the various SAML configurations on the ADC. Note that $dom variable references the “verified” domain you will have added to Azure as a prior pre-requisite.
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("C:\idp_publicKey.crt") $certData = [system.convert]::tobase64string($cert.rawdata) $dom = "ferroque.dev" $fedBrandName = "Ferroque Dev Domain" $logoffuri = "https://idp.ferroque.dev/cgi/tmlogout" $logonuri = "https://idp.ferroque.dev/saml/login" $issueruri= "https://idp.ferroque.dev/" $ecpUrl = "https://idp.ferroque.dev/saml/login" Set-MsolDomainAuthentication -DomainName $dom -federationBrandName $fedBrandName -Authentication Federated -PassiveLogOnUri $logonuri -SigningCertificate $certData -IssuerUri $issueruri -ActiveLogOnUri $ecpUrl -LogOffUri $logoffuri –PreferredAuthenticationProtocol SAMLP
Also an important note, the $issueruri variable must match the “Issuer Name” on the SAML IDP profile exactly or Azure SAML authentication issues will occur (AADSTS50107 error) as shown below once configuration is complete and you are testing the flow. In my case, the trailing slash was not present in my SAML IDP profile to match the federation configs. Easy fix.
Running the following command will give you an output to validate your configurations.
Get-MsolDomainFederationSettings -DomainName ferroque.dev
For reference, the output of the above commands should look something like this. If your trusted signing certificate did not present itself in a Base64 blob, you have an issue needing correcting for your certificate.
Examing the Azure AD GUI properties for custom domains should now show the domain as federated as well.
Moment of Truth – Validation
At this point all necessary configurations should be in place. Attempting to log into the Citrix Gateway should have the user redirected briefly to Azure AD, then to the Citrix ADC-hosted IDP. Upon successful authentication via LDAP, the user should be redirected back to Azure AD where presumably some form of MFA (token, push, etc.) will take place, at which point the user should be directed back to the Citrix Gateway and seamlessly passed through to StoreFront.
Accessing Gateway Page, Redirected to Azure and to Citrix ADC IDP. First LDAP Auth (sAMAccoutName)
Successful Response from IDP AAA to Azure – Azure Auth (MFA, etc.)
Successful Response from Azure to Citrix Gateway AAA and Passthrough 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.
## Variable and Assignment ## add ns variable idp_usercreds_var -type "map(text(256),text(256),1000)" -ifFull undef -ifValueTooBig undef -expires 3600 add ns assignment idp_usercreds_assign -variable "$idp_usercreds_var[AAA.USER.NAME]" -set AAA.USER.PASSWD.ENCRYPT ## Certificate Certkeys and Linking ## add ssl certKey MSFT_IDP_CERT -cert Ferroque-CitrixGateway-NOFAS-TEST.cer -passcrypt @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 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 IDP, SAML SP Actions ## add authentication samlIdPProfile SAML_IDP_PROF -samlIdPCertName wildcard_ferroque_dev -assertionConsumerServiceURL "https://login.microsoftonline.com/login.srf" -samlIssuerName "https://idp.ferroque.dev/" -rejectUnsignedRequests OFF -audience urn:federation:MicrosoftOnline -NameIDFormat persistent -NameIDExpr "AAA.USER.ATTRIBUTE(2).B64ENCODE" -Attribute1 IDPEmail -Attribute1Expr "AAA.USER.ATTRIBUTE(1)" -Attribute1FriendlyName mail -Attribute1Format URI -SPLogoutUrl "https://idp.ferroque.dev/cgi/tmlogout" add authentication ldapAction LDAP_FERROSYS -serverIP 188.8.131.52 -serverPort 636 -ldapBase "OU=your,DC=domain,DC=DEV" -ldapBindDn email@example.com -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 ldapAction LDAP_FERROSYS_SSO -serverIP 184.108.40.206 -serverPort 636 -ldapBase "OU=your,DC=domain,DC=DEV" -ldapBindDn firstname.lastname@example.org -ldapBindDnPassword @@@@@@@@@@@ -encrypted -encryptmethod ENCMTHD_3 -ldapLoginName userPrincipalName -groupAttrName memberOf -subAttributeName cn -secType SSL -ssoNameAttribute userPrincipalName -passwdChange ENABLED -Attribute1 mail -Attribute2 objectGUID -Attribute9 sAMAccountName -Attribute10 userPrincipalName add authentication samlAction SAML_MSFT_SRV -samlIdPCertName MSFT_IDP_CERT -samlSigningCertName wildcard_ferroque_dev -samlRedirectUrl "https://login.microsoftonline.com/xxxxxxxxxxxxxxxxxxxx/saml2?whr=ferroque.dev" -samlUserField NameID -samlIssuerName "https://gateway.ferroque.dev/" -Attribute1 NameID -Attribute2 email -enforceUserName OFF -forceAuthn ON -audience "https://gateway.ferroque.dev/" ## AAA-TM Servers ## add authentication vserver AAA_IDP SSL 220.127.116.11 443 -maxLoginAttempts 10 -failedLoginTimeout 15 add authentication vserver AAA_GATEWAYNOFAS SSL 0.0.0.0 -maxLoginAttempts 10 -failedLoginTimeout 15 set ssl vserver AAA_IDP -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 set ssl vserver AAA_GATEWAYNOFAS -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 bind authentication vserver AAA_IDP -portaltheme RfWebUI bind authentication vserver AAA_GATEWAYNOFAS -portaltheme RfWebUI bind authentication vserver AAA_GATEWAYNOFAS -policy noschema -priority 100 -gotoPriorityExpression END bind authentication vserver AAA_GATEWAYNOFAS -policy saml_sp_policy_to_aad_idp -priority 100 -nextFactor PreFillUsernamePassword_PL -gotoPriorityExpression NEXT bind authentication vserver AAA_IDP -policy SAML_IDP -priority 100 -gotoPriorityExpression NEXT bind authentication vserver AAA_IDP -policy authsrv_ldaps_authOn_sAMAccountName -priority 100 -nextFactor Assign_StoreCreds_PL -gotoPriorityExpression NEXT bind ssl vserver AAA_IDP -certkeyName wildcard_ferroque_dev bind ssl vserver AAA_GATEWAYNOFAS -certkeyName wildcard_ferroque_dev add authentication authnProfile AAA_GATEWAY-NOFAS_authprof -authnVsName AAA_GATEWAYNOFAS -AuthenticationHost gateway.ferroque.dev -AuthenticationDomain ferroque.dev ## Gateway Server ## add vpn vserver gateway.ferroque.dev SSL 18.104.22.168 443 -downStateFlush DISABLED -Listenpolicy NONE -tcpProfileName nstcp_default_XA_XD_profile -maxLoginAttempts 10 -failedLoginTimeout 15 -authnProfile AAA_GATEWAY-NOFAS_authprof add vpn sessionAction BASELINE_SESSION_WEB_PROF -sessTimeout 60 -clientSecurityLog ON -splitTunnel OFF -transparentInterception ON -defaultAuthorizationAction ALLOW -ssoCredential PRIMARY -icaProxy ON -wihome "https://storefront.ferroque.dev/Citrix/StoreWeb" -ntDomain ferroque.dev -clientlessVpnMode OFF add vpn sessionPolicy BASELINE_SESSION_WEB_POL true BASELINE_SESSION_WEB_PROF bind vpn vserver gateway.ferroque.dev -staServer "https://xdc1.ferroque.dev" bind vpn vserver gateway.ferroque.dev -staServer "https://xdc2.ferroque.dev" set ssl vserver gateway.ferroque.dev -ssl3 DISABLED -tls1 DISABLED -tls11 DISABLED -dtls1 DISABLED -HSTS ENABLED -maxage 157680000 bind vpn vserver gateway.ferroque.dev -portaltheme RfWebUI bind vpn vserver gateway.ferroque.dev -policy BASELINE_SESSION_WEB_POL -priority 100 -gotoPriorityExpression NEXT -type REQUEST bind ssl vserver gateway.ferroque.dev -certkeyName wildcard_ferroque_dev ## Login Schemas, Policies, Policy Labels ## add authentication loginSchema PreFillUsernamePassword_LS -authenticationSchema noschema -passwdExpression "$idp_usercreds_var[AAA.USER.NAME.TO_LOWER.BEFORE_STR(\"@\")].DECRYPT" -userCredentialIndex 5 -passwordCredentialIndex 6 -SSOCredentials YES add authentication loginSchema StoreCreds_LS -authenticationSchema noschema -SSOCredentials YES add authentication samlIdPPolicy SAML_IDP -rule "HTTP.REQ.HEADER(\"Referer\").CONTAINS(\"login.microsoftonline.com\")" -action SAML_IDP_PROF add authentication noAuthAction NO_AUTHN add authentication loginSchemaPolicy noschema -rule true -action LSCHEMA_INT add authentication Policy authsrv_ldaps_authOn_sAMAccountName -rule true -action LDAP_FERROSYS add authentication Policy store_creds_policy_finish -rule true -action NO_AUTHN add authentication Policy saml_sp_policy_to_aad_idp -rule "HTTP.REQ.COOKIE.NAME_VALUE(\"NSC_TASS\").CONTAINS(\"idp.ferroque.dev\").NOT" -action SAML_MSFT_SRV add authentication Policy store_creds_policy -rule true -action idp_usercreds_assign add authentication Policy authsrv_ldaps_authOn_SSO -rule true -action LDAP_FERROSYS_SSO add authentication policylabel Assign_StoreCreds_PL -loginSchema StoreCreds_LS add authentication policylabel PreFillUsernamePassword_PL -loginSchema PreFillUsernamePassword_LS bind authentication policylabel Assign_StoreCreds_PL -policyName store_creds_policy -priority 100 -gotoPriorityExpression NEXT bind authentication policylabel Assign_StoreCreds_PL -policyName store_creds_policy_finish -priority 110 -gotoPriorityExpression NEXT bind authentication policylabel PreFillUsernamePassword_PL -policyName authsrv_ldaps_authOn_SSO -priority 100 -gotoPriorityExpression NEXT
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:
shell 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:
shell tail -f /var/log/ns.log
Examining policy hits on nFactor configuration:
shell nsconmsg -d current -g pcb_hits
The SAML-Tracer extension for Chrome or FireFox is also an invaluable tool for debugging SAML authentication.
Additionally, an error message such as this below may indicate the variable is not successfully finding a matching user ID in the map in order to pull the password, or the user ID itself is not being passed through correctly. The former would show up as an error in the aaad.debug logs about a null password error. The latter may indicate the user was not found when performing the SSO LDAP config (the second LDAP auth in the sequence). In such case there may be an issue with the SSO LDAP server config such as sAMAccountName used instead of userPrincipalName for Server Logon Name Attribute, or some other mismatch between NameID received back from Azure’s assertion and the Server Logon Name Attribute defined on the SSO LDAP server on the ADC.
Special thanks to Citrites including Rene Gamache, Florin Bejan, Maude Courcy, Blair Parker, Saman Salehian, and Citrix Alumni Jay Chandrasekar. And an especially huge shout out to Citrix’s Dileep Reddem for his mastery on the “under the hood” internals of ADC. Without his generous assistance this solution would likely have taken much longer.
Amazing article, thank you so much for putting this together. I can only imagine the efforts put in by you and everyone else involved. It really helped us as we wanted a solution without FAS. Just wanted to mention a minor thing I noticed in the article, I was following the GUI method to create the vServers and Polices etc and didn’t pay attention to the commands initially, got confussed at Step#8 with this comment, “Edit the properties of the non-addressable AAA vServer used by Citrix Gateway (AAA_GATEWAYNOFAS). Bind the SAML SP policy created earlier by clicking “Authentication Policy”. I… Read more »
Hi! Thanks so much for your feedback and excellent catch. I’d documented creating the SAML SP server (for Azure) but not the policy itself. I have updated Step 3 to include that detail.
Hello Michael, as Retheesh said this is an amazing article, almost my same situation, the difference is that in my we do have adfs as an IDP for azure, so my question is how the integration is done? I’ve also read the okta article, and my guess is a mix of both, but I’m stuck because I’m thinking of two scenarios, first when in corporate network, authentication goes through SSO on ADFS ( NS -> AzureAD saml -> ADFS SSO -> SF), but on an external network ADFS asks for user and pwd (NS -> AzureAD saml /input username ->… Read more »
Hi Jorge, the solution requires ADC act as the IDP. If you’re already using ADFS as an IDP, then per the suggestion earlier on in the post, you’d likely need to create a new domain for your Citrix users to access, federate that in Azure with your ADC IDP. This is because Azure can only work with one IDP per federated domain. So if domain.com is federated to ADFS already in Azure, you would need to get a new domain like mydomain.com, and federate it in Azure with your ADC IDP you’d create per this article. This would mean your… Read more »
If domain.com is already federated would my.domain.com work?
Hi Michael, Very good article the only thing i dont understand is the idp.ferroque.dev where are you getting this? thanks in advance
Hi Farooq, we created the IDP earlier in the article; Citrix ADC has to act as a SAML IDP and thats the name this walkthrough uses for the IDP. Please elaborate more if I’m not getting the gist if your question.
Hi Michael, i do see in section 3 service provider url you defined idp.ferroque.dev and under issuer. But do we define in azure application and does this name resolve to ADC gateway ip or we just pack any name
Hi Farooq, the article includes steps for setting up the IDP federation between Azure and your IDP. For the Citrix Gateway “application” we do not specify the IDP, specify the Citrix Gateway URL.
So in this article your idp is geteway.ferroque.dev? In Azure config you are showing gateway.ferroque.dev and in SAML IDP you are entering idp.ferroque.dev, 2 different names that’s where i am confused.. I believe i will be using gateway.ferroque.dev for my test lab correct? Sorry i am just lost
Hi Michael, we noticed that if a user type an incorrect password on the Netscaler IDP login window, it still gets forwarded to https://login.microsoftonline.com/login.srf and Azure returns this error message “AADSTS51004: The user account does not exist in the directory. To sign into this application, the account must be added to the directory”. Have you seen this behavior, is this expected?
Hi Retheesh, sounds like the user is not assigned to the app in Azure.
Hi Michael, thanks for this write up! Due to some issues with FAS this seems to be a neat soloution. Unfortunately I am stuck at the “Not Allowed to login” message – but I can’t figure out why. Our UPN looks the same like the mail attribute but I am stuck within this error message. Do you know, where to look maybe first to resolve it?
[…] HowTo: Azure MFA SAML and Citrix Gateway with SSO Without FAS – Michael Shuster […]
Hi Michael, In the command line reference, line # 59 “add authentication noAuthAction NO_AUTHN”. Is this required? I couldn’t find a reference to this anywhere else in this article.
Hi there, look at the last picture in step 6.
Line# 62 created that policy. “add authentication Policy store_creds_policy_finish -rule true -action NO_AUTHN”. So I was not sure if line #59 was still required.
Excellent article! In our environment we use Ping Federated an on premise IDP. Have you done any testing with Ping. In that scenario would we need to still create a separate domain?
Hi Shelton, it’s on the list, have a dev account, but have not yet had time to solution Ping yet. It really should work the same. Note the IdP for a separate domain would depend entirely on whether or not Ping would let you go to an alternative IdP for a specific app you configure or if it’s tenant-wide. In which case an alt. domain to federate against might be needed as suggested. I’d be interested in seeing if Ping has means to replicate what we can do with Okta and the LDAP POST function (another of our blogs) to… Read more »
Hi Michael, I’m trying to set it up so the user can enter UPN as well. I’ve created a new LDAP server/policy for UPN similar to the SAM one, but for UPN and assigned it to the ADC IDP virtual server similar to the SAM one. I’m able to login to the ADC IDP using UPN, MFA on Azure, but then get “You are not allowed to login. Please contact your administrator.” on the ADC. Any ideas? Would you be able to provide the config to use UPN?
I should also mention that I’m getting “Null password check failed in ldap authentication: 1” in the aaa debug logs.
I have it working now for one of my domains. I created a new Assignment with Key Expression set to “AAA.USER.NAME”, created a new Store Creds policy with the new Assignment as the action, created a new Policy Label using the new Store Creds policy, and set the UPN LDAP server SSO Name Attribute to “sAMAccountName”. The LDAP server is set for Global Catalog and it looks to be working for users on the domain the LDAP server is set to. It’s not working for users on other domains though. That’s something I’ll need to figure out. I may need… Read more »
Hi Michael, This is great stuff. I was thinking about this design in a scenario where you have an Active-Active GSLB configuration across multiple sites for the Gateway. Because there’s no concept of persistence groups with GSLB and you can’t share a GSLB vServer Persistence ID between multiple vServers on the same ADC – I was curious if you had any thoughts on how you could avoid a scenario where the user hits gateway.company.com on one ADC, but is directed to idp.company.com on a DIFFERENT ADC in your GSLB topology. My initial thought is tweaking some things and using a… Read more »
Oh that’s a good one. Active-Passive would work and this has been deployed before (was actually the basis for the original solution). Active-Active Gateways I do not do as much on these days as EPA doesn’t play nice in A-A config. I’ll need to ask internally for other ideas on this one.
I remembered you can’t bind a AAA vServer to a Content Switch if a VPN vServer is already bound – so my Content Switch thought doesn’t work. Still pondering…
As a long-suffering FAS admins, this article has us seriously considering trying to replace it. Microsoft has some documentation titled “Azure Active Directory single sign-on integration with Citrix ADC SAML Connector for Azure AD” which seems to suggest that SSO is achievable through Kerberos delegation without needing to configure the Citrix gateway as an IdP which is federated with Azure AD. This strikes me as a simplified solution but I’m not sure if it has any downsides or drawbacks as compared to the IdP solution summarized in this article. Could you please provide your thoughts? Thank you!
Hi John, I have to say if FAS is already in place, it’s often a set-it-and-forget-it situation for most customers once it’s up and running (other than maybe renewing the Reg Authority cert every 2 years which is the default). The article uses KCD which is something Citrix used for SAML auth. back in the XenApp 6.5 days with StoreFront but was abandoned in favour of FAS due to some inherent challenges I now forget. Their config is also for web servers in their example, not necessarily for establishing a logged-in session to a Windows server. So without labbing this… Read more »
thanks for this article. I try it in my environment and see a problem after the authentication from the IDP before the it goes back to Azure.
The aaad-log says:
start_cascade_auth 0-4136: starting cascade authentication
cascade_auth 0-4136: local db rejects : email@example.com
ascade_auth 0-4136: NOAUTH sending accept to kernel for: firstname.lastname@example.org, last error: 4001
i think this is the issue which caused in the last step back to the gateway a “You are not allowed to login”. Do you have a idea why the “local DB rejects”.
in the meantime i see that it`s a problem with the assignment “idp_usercreds_assign” on the AAA IDP. If i try to set “AAA.USER.PASSWD” the i receive the error “local db rejects”. It looks like all “AAA.USER.*”, “AAA.LOGIN.*” are not working with my 13.0 82.45. Do you know is this a release specific problem or do you have a other idea?
It`s working now. The log message “local db rejects” is not a indicator for the error. There was a mismatch between SAM and UPN. Thanks for the help.
Trying to POC this in our lab. I’ve got it working when the username portion of the UPN matches that of the sAMAccountName. However, for most customers they’ll have a different username portion of the UPN vs sAMAccountName e.g. sam: username, upn: first.last@company You’ve stated that with some modifications to SAML and LDAP properties you can get this working, but I’m drawing blanks at the moment Then I logon as a user “test” who’s UPN is test.user@tld I get this error in ns.log: Aug 23 14:42:38 <local0.err> 10.1.1.10 08/23/2021:02:42:38 GMT NS1 0-PPE-0 : default SSLVPN Message 2044 0 : “Error while… Read more »
I got it working in the end, I just configured the sAMAccountName LDAP server to SSO with UPN using the SSO Name Attribute. So the user types “username” and the NetScaler stores the credentials in UPN format (at a guess). I’m not sure how to query the stored credentials..
Then configured the label schema expression as:
Awesome James, I am sure that will be useful for others. Thanks for sharing!
This is a very detailed blog and very good material around how you laid it all out.
So this was going well until our customer started to test this with users. It seems that if there’s a delay in answering the MFA prompt, when MS redirects back to https://gateway.fqdn/cgi/samlauth the NetScaler returns this error: Http/1.1 Internal Server Error 43531 Jan 26 10:22:54 <local0.err> 10.200.17.7 01/26/2022:09:22:54 xx 0-PPE-0 : default SSLVPN Message 280220752 0 : “Error while extracting username/password from request 2” Seems to be if there is a ~30 second delay in answering the MFA prompt you get the above error. I’ve replicated this issue across two different NetScaler’s on two different versions so I’m at a loss.. have started… Read more »
May be because it times out and is trying to send an error message back to ADC, which ADC cannot understand. I have seen the same thing with Ping. So Support may not be able to help but might be more of a request for enhancement. Side note if they tell you this config is unsupported, they are mistaken. The config of this article I’ve confirmed is supportable by the Product Manager and this article should eventually make its way onto Citrix Tech Zone (I just wrapped up QAing the Okta one) to further reinforce that.
Hi Michael, we are thinking about such an implementation but not sure which ports need to be opened to have the ADC connected to AzureAD, that the whole process will work. Is there any link or kb article which could help here?
best regards and thank you
Hey Kai, this is SAML so technically ADC doesn’t talk directly to AzureAD. As long as the user can connect to AzureAD, and to the ADC, that’s how the assertion flows.
I’ve been successful in configuring this without FAS and without the need to configure the ADC as an IDp by simply telling the Storefront to trust the gateway for delegation, just as one would do in a smartcard implementation.
Then you aren’t getting SSO to the desktop or app, which is the point of FAS (and by extension this post).
We’ve found that with the delegation enabled, the user is only prompted once.
That sounds intriguing. Can you share a little bit about your environment? How the VDAs are joined, what authentication method is being used (and what, if any, supported agents are in the environment for the product), etc.?
Hi, thanks for this guide – there doesn’t seem to be much documentation available for setting up SAML without FAS. We only have one domain in Azure, so I’m a bit worried about the federation in step 10 and the effect it might have. It’s not federated to anything at the moment, but all the user accounts are in that domain.