HowTo: Create a Citrix ADC nFactor Custom Label Cancel or Logoff Button


Introduction and Background

We’ve recently been working with a customer requiring some minor enhancements to the authentication flow by scrutinizing access via group membership. In practice, the nFactor flow is pretty simple with passthrough factors (using NOAUTH and no login schemas) however what to do with users who are NOT in the approved groups was a bit of a challenge.

The customer was migrating off of F5 APM (as all should do =D ) and this was handled in their authentication flow. Group membership filtering is something we can do on the session policies themselves. They wanted to keep with a similar authentication flow as their prior deployment for familiarity’s sake.

Below is the group selector example. If the user is found to be a member of the given AD group following a prior LDAP factor, their journey ends, and they’re sent over to StoreFront (in our case).


However what of users who are NOT in the group(s) defined in the first policy expression? If we do not include a policy for them, Citrix ADC will generate a “Cannot complete your request” error which is not particularly smooth for the user. We need to do SOMETHING with them in order to gracefully terminate the flow.

In the screenshot above, you’ll see the users that fail the group membership check are passed onto another factor. In that policy label, we have a custom login schema (which we’ll walk through later) that merely generates a custom error message (not unlike how the EULA login schema renders text).

The challenge, however, is that by default the nFactor construct buttons defer to a “submit” \ “next” kind of action. We need a cancel or logoff button to terminate the flow. You’d think even something like the EULA login schema would include a “Cancel” function in the event a user did not agree to the terms, but alas it does not. So we needed to hunt down a way to do this elegantly. We could just remove a button altogether from the login schema XML file and within about a minute or so an authentication timeout message will appear and handle the job for us, but we’re really just trying to tune the user experience in our use case.


  • Intermediate knowledge of Citrix ADC’s nFactor constructs. For getting up to speed, Citrix docs are here, and Carl Stalhood always has a great consolidated reference set found here.
  • A RfWebUI-based portal theme created on the appliance. It must be RfWebUI-based!
  • A functioning AAA vServer set up with TLS certificate and functioning LDAP policy or policy label tested and in place. The custom portal theme should be bound to it and your Citrix Gateway vServer (if applicable).
  • Superuser \ nsroot access to the Citrix ADC and means to SCP and SSH to the appliance.
  • (Optional) Some prepared text for the custom error page we’ll define in the login schema. In our use case and example here, we are adding a cancel button to the “end of the road” authentication failure.

Step 1 – Create Login Schema

Starting out, we’ll create a new login schema XML file. These files are stored in the appliance under “/nsconfig/loginschema/LoginSchema”. You can clone the Eula.xml file or copy-paste the code below. Once complete, place the file in that location via SCP client.

There are a few things to note here in the file:

  • We are removing the line “<PostBack>/nf/auth/</PostBack>” from the default XML config as a precautionary measure as we want to avoid anything that might result in the ADC thinking the action of clicking the button is a success condition and proceed in the authentication flow and ‘might’ allow the user to successfully hit the StoreFront URI if they happen to know it. This is a paranoid stab in the dark on my end, the actual inner workings of the login schema constructs are not publically documented. On that note, you may notice there is a “CancelPostBack” function, but incidentally, that component of the ADC does not appear to be functional. Manipulating the XML file to perform the “” action sends the button into a spinning black hole when clicked, so a red herring. It was an early attempt at least vs. what we ultimately accomplished.
  • We have a custom button with an ID of “nsg-logoutBtn” which we will reference in our custom label.
  • We have an auth requirement component which we’ve labeled “cancelbutton” which will trigger out custom label action we’ll define in the next step.
  • We have two text components in the example. One is a header for the error condition, the other is body text to describe the custom error message we want the user to see.


Below is the code for the XML file in the example.

<?xml version="1.0" encoding="UTF-8"?>
<AuthenticateResponse xmlns="">
<Status>success </Status>
<Requirement><Credential><Type>none</Type></Credential><Label><Text>Access Denied</Text><Type>heading</Type></Label><Input /></Requirement>
<Requirement><Credential><Type>none</Type></Credential><Label><Text>Your Active Directory group membership does not authorize you to access this platform.</Text><Type>plain</Type></Label><Input /></Requirement>
<Requirement><Credential><Type>none</Type></Credential><Label><Text></Text><Type>plain</Type></Label><Input /></Requirement>

With the XML file created on the appliance, we need to create the login schema profile via Security>AAA – Application Traffic>Login Schema>Profiles tab. Create a new login schema object and select using the pencil icon the XML file we created.


Pro Tip: Somewhat of a bug but I’ve found on different appliances, even those running the same firmware, that when going to select an XML file, you may get a random error in the web GUI that prevents you from selecting the file. Obnoxious. To get the job done, either SSH to the appliance or under System>Diagnostics launch the CLI tool. Use the following command (altered to your need) to select the file.

add authentication loginSchema lschema_failed_groupcheck -authenticationSchema "/nsconfig/loginschema/LoginSchema/FailedGroupCheck.xml"

Step 2 – Create Custom Label Action

In this step, we need to navigate to the theme you are using on your vServers as we need to edit the script.js file contained within it to add the necessary JavaScript code. The location of this file will be: “/var/netscaler/logon/themes/<YOUR_THEME>/script.js”.

Within the script.js file, append the following text as shown below.


Items to note in this config:

  • We are triggering on the “cancelbutton” ID to trigger the custom label action we are defining.
  • We are overriding the button behaviour with a redirect link when clicking it. We are doing so by affecting the #nsg-logoutBtn” parameter which we associated with our button in the login schema.
  • For the website redirection upon clicking the button, we are intentionally going to “/cgi/tmlogout” because doing so terminates the AAA session which is our intended result, vs. allowing the error page to time out (it still will on its own). This allows the user to more rapidly terminate the authentication session. The user is dropped back onto the first authentication factor when clicking the button.

The code for the script.js file can be found below for easy copy-paste reference.


  // Credential type defined in login schema
  getCredentialTypeName: function() { return "cancelbutton"; },

  // Generate HTML for the custom credential
  getCredentialTypeMarkup: function(requirements) {





Step 3 – Create an Authentication Policy and Policy Label

Now that we have our core components, go ahead and create a non-authenticating authentication policy (i.e. select NO_AUTHN as the action).


Next, create the policy label in which we’ll select our new customized login schema, and bind the authentication policy we just created.


Pro Tip: Whenever you MODIFY a login schema’s XML file (say, to tweak function or verbiage) you MUST go back and edit the login schema profile and re-select the XML file. This reloads it into the appliance. If you don’t, your changes won’t materialize!

Lastly, bind the NO_AUTHN policy created earlier in this step and select “END” as the “GOTO EXPRESSION” we have completed our authentication journey. At a dead end.


The commands used for the above are found below.

add authentication Policy authpol_groupcheck_fail_result -rule true -action NO_AUTHN
add authentication policylabel authpl_groupcheck_failed -loginSchema lschema_failed_groupcheck
bind authentication policylabel authpl_groupcheck_failed -policyName authpol_groupcheck_fail_result -priority 100 -gotoPriorityExpression END

Step 4 – Bind Policy Label as Next Factor

This brings us full circle to the screenshot used at the start of this post, where we have another non-authenticating factor performing a group selection step (has to follow a preceding LDAP group extraction step or reference groups local to the appliance). We select for the “failed” policy our new policy label as the next factor.


And for those interested in the code for that (obviously swap out BOGUS for something else), you can find the code below. Note if you are doing multiple groups, you can do II statements in the “pass” expression to chain them as “or”, but be sure to use && statements in the “fail” expression to indicate a fail result must result in not matching any of the groups.

add authentication Policy authpol_groupcheck_pass -rule "AAA.USER.IS_MEMBER_OF(\"BOGUS\")" -action NO_AUTHN
add authentication Policy authpol_groupcheck_fail -rule "AAA.USER.IS_MEMBER_OF(\"BOGUS\").NOT" -action NO_AUTHN
add authentication policylabel authpl_groupcheck -loginSchema LSCHEMA_INT
bind authentication policylabel authpl_groupcheck -policyName authpol_groupcheck_pass -priority 100 -gotoPriorityExpression END
bind authentication policylabel authpl_groupcheck -policyName authpol_groupcheck_fail -priority 110 -gotoPriorityExpression NEXT -nextFactor authpl_groupcheck_failed

Step 5 – Kick the Tires (Test!)

Provided the rest of your AAA config is functional, the result should be as follows if the authenticating user is not in the desired group.


And clicking the “Cancel” button should throw us back to the login screen, ending the authentication session.



My colleague Rene casually reminded me this intricacy could be avoided by simply creating yet another NO_AUTHN policy and policy label to link to the “failed” one, and using the custom labels autopost functionality (in this case to the logout page) I previously documented in this article, and do away with repurposing the button itself. Pick your poison.


Hopefully, this helps a few people who need to address some niche use cases. I’d like to express my thanks to my colleagues Rene Gamache and Edson Da Luz over at Citrix, and Ferroque’s Michael Wieloch for his JavaScript chops.

0 0 votes
Article Rating
Notify of
1 Comment
Inline Feedbacks
View all comments
Jeffrey Faulstich
Jeffrey Faulstich
4 months ago

Is there a URL Path that you could have the Cancel refer to that would clear the http.req.cookie values?

I’m finding with “/cgi/tmlogout” the standard “manageotp” is not cleared out which means the user is stuck in the manageotp path mode when using the
Expression: http.req.cookie.value(“NSC_TASS”).contains(“manageotp”)

I’m trying to make it so the “Cancel” would ensure the session is logged off and that any cookies are cleared that could play into any end-user issues.

Would love your thoughts, please comment.x