Those who know me are aware that I’ve got a keen interest in two-factor authentication.  So, I was very interested when my colleague, Chris Jeffrey, called attention to the fact that two-factor doesn’t work with multiple NetScaler Gateway logon points when using specific customized logon pages.
Specific customized logon pages allow you to display a different logon page for each NetScaler Gateway vServer.  This could be a real advantage in a cloud-hosting environment where multiple customers connect via the same VPX and each logon point needs to include unique company branding elements.

But, a significant problem occurred because users don’t see a second password prompt on any customized logon point beyond the first. The password prompt used for PIN or token simply doesn’t appear despite CTX123736 having been followed perfectly even with the most minimal of customizations and it’s not at all clear why. I like a puzzle and decided to investigate.

Read on to learn the cause and the solution.

First NetScaler Gateway vServer

Second NetScaler Gateway vServer customized as per CTX123736.
Note: the lack of PIN code prompt!

 
 
 
 
 
 
 
 
 
 
After a quick look at the NetScaler Gateway files we find the second password prompt.
The prompt, which we’ve customized to say “PIN code” rather than “Password 2”, isn’t actually static HTML.  Rather, the prompt is being added to the logon page dynamically using a JavaScript file called “login.js” which contains some conditional statements.

function ns_showpwd()
{
var pwc = ns_getcookie(“pwcount”);
document.write(‘<div><div><label><SPAN>’ + _(“Password”));
if ( pwc == 2 ) { document.write(‘&nbsp;’); }
document.write(‘:</SPAN></label></div>’);
document.write(‘<div><input autocomplete=”off” spellcheck=”false” type=”Password” title=”‘ + _(“Enter password”) + ‘” name=”passwd” size=”30″ maxlength=”127″></div></div>’);
if ( pwc == 2 ) {
document.write(‘<div><div><label><SPAN>’ + _(“Password2″) + ‘</SPAN></label></div><div><input autocomplete=”off” spellcheck=”false” type=”Password” title=”‘ + _(“Enter password”) + ‘” name=”passwd1″ size=”30″ maxlength=”127″></div></div>’);
} UnsetCookie(“pwcount”);
}

By examining the JavaScript, we see the display of the second password prompt is only executed if a cookie called “pwcount” exists, and if it has a value of 2. Checking the vServers with Firebug we find the cookie exists on the first (which is working and displaying the prompt), but not the second.
    
After a quick email exchange with engineering, the problem is later clarified: The pwcount cookie is actually only set when retrieving two specific filenames, index.html and tmindex.html – as our 2nd vServers was customized by following CTX123736 we’re retrieving index_modified.html
 

Now, an immediate solution would be to:

Remove the conditional statements in a modified login.js file and thereby hardcode the second password prompt to always be displayed. It’s a quick solution that solves the immediate issue.  But, editing JavaScript isn’t very dynamic.  Now that we know the cause, wouldn’t it be nicer to have a solution which can be enabled and disabled via the GUI?
 

“Ah-ha, perhaps we could set the cookie using a rewrite rule bound to the NetScaler Gateway vServer?”

    add rewrite action Add_pwcount_cookie insert_http_header Set-Cookie “\”pwcount=2\””

    add rewrite policy Enable_pwcount_cookie_policy “!HTTP.REQ.HEADER(\”Cookie\”).CONTAINS(\”pwcount\”)&& HTTP.REQ.URL.STARTSWITH(\”/vpn/index_modified.html\”)”

    add_pwcount_cookiebind vpn vserver “Test NSG” -policy Enable_pwcount_cookie_policy -priority 100 -gotoPriorityExpression END -type RESPONSE

Initially this didn’t work. An integrated caching policy called “_cacheVPNStaticObjects” is configured by default and removes cookies before delivering the cached page. The solution was to modify that policy not to apply to our index_modified file.

    set cache policy _cacheVPNStaticObjects -rule “HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH_ANY(\”vpn_cache_dirs\”) && !HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH(\”/vpns/portal/\”) && !HTTP.REQ.URL.PATH_AND_QUERY.STARTSWITH(\”/vpn/index_modified.html\”)” -action CACHE

This works and it’s far more dynamic.

You can now enable / disable the second password prompt simply by binding and unbinding a single rewrite policy from that vServer.