NetScaler – EPA Scans with Quarantine Group

Reading Time: 5 minutes


Recently a customer had to switch from User-Cert Authentication (CBA) to Device-Cert Authentication, so I had to create a new nFactor flow with EPA for Device-Cert Check. This Post will cover the following requirements:

  • EPA Scan for Device-Cert Check (Windows, macOS), followed by Username+Password
  • No-EPA Scan for Non-EPA supported Devices (iOS, Android – Otherwise adding an Account in Workspace App will fail instantly), followed by Group Extraction with native OTP
  • Fallback (Quarantine Group) to Group Extraction with native OTP for failed EPA-Scan / “Skip Check” Button usage for external (Non-Corp) Users


The complete Configuration is attached as CLI, here are just some informations to note and why I chose that way of design.

First, to understand how EPA works in nFactor, this is very important when starting with your requirements:

As soon as the first policy is bound to an EPA action, followed policies with higher priorities are limited to NO_AUTHN policies.

To gain that maximum flexibility and choose which User-Agent / Client get’s EPA and who not, I’m starting with these two policies on my AAA vServer:

Also, I’ve NOT worked with inbuilt Default Group and Quarantine Group in EPA action:

Instead, I’ve used No-Auth actions to assign default authentication groups and giving the different Tags for Default and Quarantine, assigning local AAA groups to differ between Corp and Non-Corp devices:

I’ve used the User-Agent expression


so EPA kicks in only when connecting through Google Chrome, Microsoft Edge or Citrix Workspace App on Windows and macOS.

#####EPA Device-Cert Scan for supported EPA-Devices followed by Username + Password, with Fallback (or "Skip Check") to nOTP as next Factor
#####Working with Default and Quarantine Groups (local AAA Groups) for assigning different Session Policies
#####For the policy label NoCert_GroupExtraction_2FA - insert your preferred MFA solution as Non-EPA Fallback
#####Bind your preferred different Session Policies to the local AAA groups for different behaviour after authentication, for example separate Store / CVPN / SPA
#####Binding your internal Sub / Root CA's on AAA and NSGW is a must-have when checking Device-Certs with EPA-Scan, replace CustomerInternal-SubCA and CustomerInternal-RootCA with your Certnames

add authentication epaAction PreAuth_EPA_DeviceCert -csecexpr "sys.client_expr(\"device-cert_0_0\")"
add authentication ldapAction Auth_LDAPS_No_Filter -serverIP -serverPort 636 -ldapBase "dc=contoso,dc=local" -ldapBindDn serviceuser@contoso.local -ldapBindDnPassword Pa$$w0rd -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName CN -secType SSL -passwdChange ENABLED -nestedGroupExtraction ON -maxNestingLevel 5 -groupNameIdentifier cn -groupSearchAttribute memberOf -groupSearchSubAttribute cn
add authentication ldapAction Auth_LDAPS_NoAuth -serverIP -serverPort 636 -ldapBase "dc=contoso,dc=local" -ldapBindDn serviceuser@contoso.local -ldapBindDnPassword Pa$$w0rd -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName cn -secType SSL -authentication DISABLED -passwdChange ENABLED -nestedGroupExtraction ON -maxNestingLevel 5 -groupNameIdentifier cn -groupSearchAttribute memberOf -groupSearchSubAttribute cn

add authentication noAuthAction NoAuth-Assign-AAA_local_defaultGroup -defaultAuthenticationGroup AAA_local_defaultGroup
add authentication noAuthAction NoAuth-Assign-AAA_local_quarantinedGroup -defaultAuthenticationGroup AAA_local_quarantinedGroup

add authentication Policy Adv_Pol_PreEPA_EvaluateHeader -rule "HTTP.REQ.HEADER(\"User-Agent\").CONTAINS(\"Chrome\")||HTTP.REQ.HEADER(\"User-Agent\").CONTAINS(\"Edg\")||HTTP.REQ.HEADER(\"User-Agent\").CONTAINS(\"CWAWEBVIEW\")" -action NO_AUTHN
add authentication Policy Adv_Pol_PostEPA_quarantinedGroup_NoFilter -rule true -action NO_AUTHN
add authentication Policy Adv_Pol_EPA_DeviceCert -rule true -action PreAuth_EPA_DeviceCert
add authentication Policy Adv_Pol_LDAPS -rule true -action Auth_LDAPS_No_Filter
add authentication Policy Adv_Pol_LDAPS_PostEPA_NoAuth -rule true -action Auth_LDAPS_NoAuth
add authentication Policy Adv_Pol_LDAPS_PostEPA-AssignTag-defaultGroup -rule true -action NoAuth-Assign-AAA_local_defaultGroup
add authentication Policy Adv_Pol_LDAPS_PostEPA-AssignTag-quarantinedGroup -rule true -action NoAuth-Assign-AAA_local_quarantinedGroup

add authentication policylabel NoSchema-AssignTag-defaultGroup -loginSchema Schema_NoLoginSchema
add authentication policylabel NoSchema-AssignTag-quarantinedGroup -loginSchema Schema_NoLoginSchema
add authentication policylabel NoSchema-Evaluate-defaultGroup -loginSchema Schema_NoLoginSchema
add authentication policylabel NoCert_GroupExtraction_2FA -loginSchema LSCHEMA_INT
add authentication loginSchema Schema_UsernameAndPassword -authenticationSchema "/nsconfig/loginschema/customer/UsernameAndPassword_NoPrefill.xml"
add authentication loginSchema Schema_OnlyUsername -authenticationSchema "/nsconfig/loginschema/customer/Only_Username.xml"
add authentication policylabel LDAP_UsernameAndPassword -loginSchema Schema_UsernameAndPassword
add authentication policylabel OnlyUsername_PostEPA -loginSchema Schema_OnlyUsername

bind authentication policylabel OnlyUsername_PostEPA -policyName Adv_Pol_LDAPS_PostEPA_NoAuth -priority 100 -gotoPriorityExpression NEXT -nextFactor NoCert_GroupExtraction_2FA

bind authentication policylabel NoSchema-AssignTag-defaultGroup -policyName Adv_Pol_EPA_DeviceCert -priority 90 -gotoPriorityExpression NEXT -nextFactor NoSchema-Evaluate-defaultGroup
bind authentication policylabel NoSchema-AssignTag-defaultGroup -policyName Adv_Pol_LDAPS_PostEPA-AssignTag-quarantinedGroup -priority 100 -gotoPriorityExpression NEXT -nextFactor OnlyUsername_PostEPA

bind authentication policylabel NoSchema-AssignTag-quarantinedGroup -policyName Adv_Pol_LDAPS_PostEPA-AssignTag-quarantinedGroup -priority 100 -gotoPriorityExpression NEXT -nextFactor OnlyUsername_PostEPA

bind authentication policylabel NoCert_GroupExtraction_2FA -policyName Adv_Pol_LDAPS_EmergencyGroup -priority 100 -gotoPriorityExpression NEXT -nextFactor LDAP_PasswordOnly_Emergency
bind authentication policylabel NoCert_GroupExtraction_2FA -policyName Adv_Pol_2FA -priority 110 -gotoPriorityExpression NEXT -nextFactor Dual_OTP_2FA

bind authentication policylabel NoSchema-Evaluate-defaultGroup -policyName Adv_Pol_LDAPS_PostEPA-AssignTag-defaultGroup -priority 100 -gotoPriorityExpression NEXT -nextFactor LDAP_UsernameAndPassword
bind authentication policylabel LDAP_UsernameAndPassword -policyName Adv_Pol_LDAPS -priority 100 -gotoPriorityExpression END

add authentication vserver AAA_vServer_nFactor_EPA_NA SSL -certkeyNames "CustomerInternal-SubCA,CustomerInternal-RootCA"
add authentication authnProfile AAA_Prof_nFactor_EPA -authnVsName AAA_vServer_nFactor_EPA_NA

bind authentication vserver AAA_vServer_nFactor_EPA_NA -policy Adv_Pol_PreEPA_EvaluateHeader -priority 100 -nextFactor NoSchema-AssignTag-defaultGroup -gotoPriorityExpression NEXT
bind authentication vserver AAA_vServer_nFactor_EPA_NA -policy Adv_Pol_PostEPA_quarantinedGroup_NoFilter -priority 110 -nextFactor NoSchema-AssignTag-quarantinedGroup -gotoPriorityExpression NEXT

add vpn vserver evd-test.customer.com_ICA SSL 443 -dtls OFF -certkeyNames "CustomerInternal-SubCA,CustomerInternal-RootCA" -Listenpolicy NONE -tcpProfileName nstcp_default_XA_XD_profile -WindowsEPAPluginUpgrade Never -LinuxEPAPluginUpgrade Never -MacEPAPluginUpgrade Never -authnProfile AAA_Prof_nFactor_EPA

It’s also important to disable Client Security Logging in NSGW Global Settings or in your Session Profiles – otherwise a potential attacker is able to see in his EPA-Clientlog what kind of EPA-Scans are configured. Enable that setting only for troubleshooting or testing purposes.

CSA / EPA Plugin

For corp-devices where you’re managing the enrollment of the CSA / EPA Plugin, you can pre-configure the list of trusted URL’s to prevent that Popup for the User:

In C:\Users\%username%\AppData\Local\Citrix\AGEE\config.js are the trusted URL’s located.

If you click manually on “Always” the config.js looks like that, so that’s what you can publish within the EPA-Installer via your Endpoint solution.

{“name”:””,”trustAlways”:1,”url”:}],”debug logging”:true,”epaTopMost”:true,”language”:”en”,”lastUserName”:””,”local lan access”:false,”log level”:”VERB”,”use new logger”:false}

Skip Check in CWA for Windows and Mac

One point which I’ve noticed during the testing is the missing “Skip Check” Button when using Citrix Workspace App for Windows. With macOS, the Button is there and usable. Please let me know if you’re encountering the same behaviour.

CWA for Windows (2403.1):

CWA for macOS (2405):

I’ve reported to Citrix and now waiting for any feedback.

User Experience

According to the config above, this is what the Auth-Flow now looks like for the User.

Corp-Device with Client-Cert, EPA-Scan runs automatically in the background and presenting the next factor:

CWA / Browser iOS and Android:

Non-Corp-Device (No Client-Cert) via CWA / Browser – EPA Check fails or User clicking on “Skip Check” Button:


A simple requirement (from the customer perspective) which costs some testing to find the matching nFactor flow.

Make sure to work with flush cache contentgroup loginstaticobjects command on NetScaler when you’re in the process switching policies and test different EPA actions – otherwise you’re hitting cached auth-policies.

Thanks to Paul Cross from Citrix Community for giving me the idea to work with NoAuth Policies for assigning the Tags.

Leave a Reply

Your email address will not be published. Required fields are marked *