NetScaler product supports nFactor authentication from version 11.0. nFactor provides various possibilities, including; fine grained authentication based on user groups, location, etc. This article describes captcha for NetScaler login using Google’s reCaptcha.
This article describes configuring captcha with single factor. This is demonstrated for an authentication vserver. However, it is applicable for Gateway vserver using nFactor for Gateway, described in addendum.
NetScaler version 11.1 (preferred), 11.0 66.x+ (with additional steps described in addendum).
This article is organized into two parts
Administrator would have to register a domain for captcha at https://www.google.com/recaptcha/admin#list.
Once administrator navigates to the above page, below screenshot appears that can be used to add a new domain/fqdn for captcha. This fqdn must be authentication/gateway vserver’s fqdn.
NOTE: Use reCAPTCHA V2 only. Invisible reCAPTCHA is still in Beta.
Once a domain is registered, administrator is shown the “site key” and “secret key” like shown below:
SiteKey and SecretKey are grayed out for security reasons. SecretKey is supposed to be kept safe.
This completes the configuration on Google
NetScaler configuration can be broken down into these parts
Login form customization is done through “LoginSchema” entity on NetScaler. This is specified at authentication vserver, and is sent to UI for rendering the login form.
Step 1:
There is a built-in loginschema called “SingleAuthCaptcha.xml” located in /nsconfig/loginSchema/LoginSchema directory on NetScaler in latest 11.1 versions. For older 11.1 versions or 11.0 versions, this can be copied from addendum section below. When copying from the browser, care should be taken to normalize special character encodings.
It is recommended that all loginSchemas be copied from /nsconfig/loginschema/LoginSchema directory to parent directory, /nsconfig/loginschema.
Assuming administrator has copied SingleAuthCaptcha.xml to desired location, below commands add a loginSchema and bind it to authentication vserver. These commands are issued in command line interface.
Step 2:
Run the following command:
add authentication loginSchema singleauthcaptcha -authenticationSchema /nsconfig/loginschema/SingleAuthCaptcha.xml
Step 3:
Run the following command:
add authentication loginSchemaPolicy singleauthcaptcha -rule true -action singleauthcaptcha
Step 4:
Run the following command:
add authentication vserver auth SSL <IP> <Port>
add ssl certkey vserver-cert –cert <path-to-cert-file> -key <path-to-key-file>
bind ssl vserver auth –certkey vserver-cert
bind authentication vserver auth -policy singleauthcaptcha -priority 5 -gotoPriorityExpression END
There is one final step before captcha can be shown to user. This pertains to customizing the UI on NetScaler to load captcha. This is done by customizing the javascript that loads the UI.
Step 5:
The script to be modified is at /var/NetScaler/logon/LogonPoint/custom/script.js
In latest versions of NetScaler, a new script /var/netscaler/logon/LogonPoint/plugins/ns-gateway/ns-nfactor.js already has the required code for running re-CAPTCHA.
Copy the script from addendum section into script.js, if ns-nfactor.js is not present.
Once this script is copied, edit the script in the editor of choice (or edit it prior to copying it onto NetScaler) as shown below.
Step 5.1
Navigate to the section that begins with
CTXS.ExtensionAPI.addCustomCredentialHandler({
getCredentialTypeName: function () { return "nf-recaptcha"; },
You can do text search for “nf-recaptcha” to locate this.
Locate below line and replace the sitekey with what you obtained from google
var reCaptchaSiteKey = "XXXXX" //Replace this with the SiteKey from Google ReCaptcha
This completes the configuration needed to show the captcha. Let’s move on to the verification of captcha response.
Now that we added necessary configuration to display captcha, let’s add configuration to verify captcha response from browser. For this we use “webAuth” authentication policy.
Sample webauth action is defined in comments section in ns-nfactor.js script itself to make it simple for administrators. Text search for “webAuthAction” in ns-nfactor.js will find desired configuration. This is described below in step 6.
Step 6:
add authentication webAuthAction recaptcha_aaatm -serverIP <google.com ip address> -serverPort 443 -fullReqExpr q{"POST /recaptcha/api/siteverify HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 10000"+"\r\n\r\n" + "secret=SiteSecretObtainedAsPartOfRegistration&response=" + http.req.body(10000).after_str("&recaptcha=")} -scheme https -successRule "http.RES.body(1000).REGEX_MATCH(re/\"success\": true.*\"hostname\": \"FQDNRegisteredAtGoogle\"/)"
In the above action, there are some pieces that are to be filled in. These are highlighted in yellow.
Step 7:
add authentication policy recaptcha_aaatm –rule true –action recaptcha_aaatm
Step 8:
bind authentication vserver auth –policy recaptcha_aaatm –priority 1
Step 9:
If Active Directory authentication is desired, this step is needed. Otherwise this step can be ignored.
add authentication ldapAction ldap-new -serverIP X.X.X.X -serverPort 636 -ldapBase "cn=users,dc=aaatm,dc=com" -ldapBindDn adminuser@aaatm.com -ldapBindDnPassword <password> -encrypted -encryptmethod ENCMTHD_3 -ldapLoginName sAMAccountName -groupAttrName memberof -subAttributeName CN -secType SSL -passwdChange ENABLED -defaultAuthenticationGroup ldapGroup
add authenticationpolicy ldap-new –rule true –action ldap-new
Since LDAP authentication happens after captcha, we add it to second factor:
add authentication policylabel second-factor
bind authentication policylabel second-factor –policy ldap-new –priority 10
bind authentication vserver auth –policy recaptcha_aaatm –priority 1 –nextFactor second-factor
Step 10:
Depending on whether LB vserver is used for access or gateway, we need to add appropriate vservers.
Let’s assume we need LB vserver. If so, administrator needs to add one.
Add lb vserver lbtest HTTP <IP> <Port> -authentication ON –authenticationHost nssp.aaatm.com
In the above command, “nssp.aaatm.com” resolves to authentication vserver.
This completes the run time flow as well. Let’s see the screenshots for the user flow.
User navigates to LB vserver and is redirected to authentication vserver. Once authentication vserver loads the login page, it appears as shown below. X1 portal theme is used on authentication vserver. For other portal themes, UI looks as configured.
As can be noticed, “Log On” button is disabled until captcha is completed. Once user clicks “I’m not a robot”, below captcha widget appears.
User would be navigated through series of captcha images before completion page is shown below
At this point, user enters his AD credentials and submits them to NetScaler. If authentication succeeds, user is redirected to desired resource.
Below logs can be found during captcha:
Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default AAATM Message 1427 0 : "Authentication delegated to Packet engine for user1, chosen action is recaptcha_aaatm, aaad resp 9 " Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default SSLVPN Message 1428 0 : "WEBAUTH: Primary webauth request being tried, action: recaptcha_aaatm " Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default SSLVPN Message 1429 0 : "WEBAUTH: Successfully sent webauth request, POST /recaptcha/api/siteverify HTTP/1.1^M Host: www.google.com^M Content-Type: application/x-www-form-urlencoded^M Content-Length: 2319 ^M ^M secret=deleted&response=03AHJ_VuupLQ113EEBg57YJaVbqHI6dsfqc7Sz0posASFrpTQC13M8qYvs8eT4X4W45zFKEvc0tsZh7_tkQEU90CxqHD3zW07tOtGAV_LNiQqlftDcIR-iO2JXSFDnWXuXSzOPldhrgE3EIx6qLqai54EkVky_DdBN5mny_P6g9gGOLJ7Ul0MRgF9dUqiAR6n7XzKwmA0AcdT_UQNHzCN91ENbzVQimq4amRbS7vPzySK3WsAUFyTVG4OC5V__G6I9xAWaSq5nqw6tX3ryVyNe8g8xHxMIkVcafawDJGEUqgUMq_eweW8cpwSjxfmZpN9pJUsOV4KSmYP0qONwFUPkOgQAxcjYqHVFW6mmZi3aWnKxmyz12GAwo8uksgCb2tHEPTtzRzAwlZ51fGRyXy61Xh7Pz96onz68a9xqr3uOMkDGVQ-N17sTewoBcxLmsZbJhTA3QuaJGTmBPOsM6hYtBa5ZouofGlr9rWTnRY2OQGYkT47tQsmeZumwv-27zhLahU9qCSUtr7GS6tx_ZGWuTqGs6l-dZhdSPadPewHFBjK-O5Fy8H_pOKS53Hwt-gkxvk15MhalM51BqQZWEFIcoWw4y58AT9ljTleWkoOu_A2ciozR1gE-8Wb-MucEoRzEG0yPPxQ_Tk3Mt3nUcsYhGmFtwG39oP5ETJ5atR0IyMv4n0ftw4c Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default SSLVPN Message 1430 0 : "WEBAUTH: Evaluating success rule for primary webauth action recaptcha_aaatm " Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default AAATM Message 1432 0 : "(14679-1852854087) Authentication succeeded, current factor: auth, for user: user1 " Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default AAA Message 1435 0 : "nFactor: Next factor passthrough1 is configured as passthough/implicit, loginschema LSCHEMA_INT" Aug 18 02:26:13 <local0.debug> 10.217.208.250 08/18/2016:00:26:13 GMT NetsScaler 0-PPE-0 : default SSLVPN Message 1436 0 : "ns_aaa_advance_authn_policyeval: copying policylabel name passthrough1 to aaa info, type 65 for auth " Aug 18 02:26:14 <local0.debug> 10.217.208.250 08/18/2016:00:26:14 GMT NetsScaler 0-PPE-0 : default AAATM Message 1437 0 : "(0-72) Authentication succeeded, current factor: passthrough1, for user: user1 "
<?xml version="1.0" encoding="UTF-8"?>
<AuthenticateResponse xmlns="http://citrix.com/authentication/response/1">
<Status>success</Status>
<Result>more-info</Result>
<StateContext></StateContext>
<AuthenticationRequirements>
<PostBack>/nf/auth/doAuthentication.do</PostBack>
<CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack>
<CancelButtonText>Cancel</CancelButtonText>
<Requirements>
<Requirement><Credential><ID>login</ID><SaveID>ExplicitForms-Username</SaveID><Type>username</Type></Credential><Label><Text>User name:</Text><Type>plain</Type></Label><Input><AssistiveText>Please supply either domain\username or user@fully.qualified.domain</AssistiveText><Text><Secret>false</Secret><ReadOnly>false</ReadOnly><InitialValue></InitialValue><Constraint>.+</Constraint></Text></Input></Requirement>
<Requirement><Credential><ID>passwd</ID><SaveID>ExplicitForms-Password</SaveID><Type>password</Type></Credential><Label><Text>Password:</Text><Type>plain</Type></Label><Input><Text><Secret>true</Secret><ReadOnly>false</ReadOnly><InitialValue></InitialValue><Constraint>.+</Constraint></Text></Input></Requirement>
<Requirement><Credential><ID>saveCredentials</ID><Type>savecredentials</Type></Credential><Label><Text>Remember my password</Text><Type>plain</Type></Label><Input><CheckBox><InitialValue>false</InitialValue></CheckBox></Input></Requirement>
<Requirement><Credential><ID>nf-recaptcha</ID><SaveID>ExplicitForms-Username</SaveID><Type>nf-recaptcha</Type></Credential><Label><Text>Captcha:</Text><Type>plain</Type></Label></Requirement>
<Requirement><Credential><Type>none</Type></Credential><Label><Text>Please enter your credentials and verify captcha</Text><Type>information</Type></Label><Input /></Requirement>
<Requirement><Credential><ID>loginBtn</ID><Type>none</Type></Credential><Label><Type>none</Type></Label><Input><Button>Log On</Button></Input></Requirement>
</Requirements>
</AuthenticationRequirements>
</AuthenticateResponse>
(function ($) {
CTXS.ExtensionAPI.addPlugin({ name: "ns-nfactor", // Name of plugin - must match name sent in configuration initialize: function () { /* * Custom credential handler for Google ReCaptcha. * If a credential of type "nf-recaptcha" is sent in any factor, this code * will be executed. The "submit" button of the form will be disabled * by default until the captcha is completed correctly. * * Use with the below WebAuth action: * add authentication webAuthAction recaptcha -serverIP <IP Address of google.com> -serverPort 443 -fullReqExpr q{"POST /recaptcha/api/siteverify HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Content-Type: applica tion/x-www-form-urlencoded\r\n" + "Content-Length: 10000"+"\r\n\r\n" + "secret=<Secret key from Google Recaptcha>&response=" + http.req.body(10000).after_str("&recaptcha=")} -scheme https -successRule "http.RES.body(1000).REGEX_MATCH(re/ \"success\": true.*\"hostname\": \"<FQDN of the Gateway/Auth vServer>\"/)" * */ CTXS.ExtensionAPI.addCustomCredentialHandler({ getCredentialTypeName: function () { return "nf-recaptcha"; }, getCredentialTypeMarkup: function (requirements) { var reCaptchaSiteKey = "6LfFyScTAAAAAPdfN29CmnhUEybgrISKAXelYiqB"; //Replace this with the SiteKey from Google ReCaptcha var div = $("<div></div>"); var captcha = $('<div id="g-recaptcha" class="g-recaptcha" data-callback="captchaSuccess"></div>').attr("data-sitekey", reCaptchaSiteKey); var input = $('<input id="captchaResponse" name="recaptcha" type="hidden" value=""/>'); div.append(captcha, input); var script = document.createElement("script"); script.type = "text/javascript"; script.src = "https://www.google.com/recaptcha/api.js"; script.onerror = function(){ var $form = $(".credentialform")[0]; var fieldDiv = $('<div class="field CredentialTypenone"></div>'); var leftDiv = $('<div class="left"></div>'); var label = $('<div class="standaloneText label error" role="alert"></div>').text("Could not load Google ReCaptcha. Please contact your administrator."); leftDiv.append(label); fieldDiv.append(leftDiv); $($form).append(fieldDiv); }; $("head")[0].appendChild(script); $(document).on('ctxsformsauthenticationdisplayform', function (e, data) { var $form = data.authenticationElement.find('.credentialform'); var $captcha = $form.find('#g-recaptcha'); $loginButton = $form.find('#loginBtn'); if ($captcha.length && $loginButton.length) { // This is a Captcha form // Disable the submit button by default disableFormsButton($loginButton); } }); return div; } }); /* End ReCaptcha code */ } }); })(jQuery); var $loginButton; function captchaSuccess(data) { $("#captchaResponse").val(data); enableFormsButton($loginButton); } function disableFormsButton($button) { // Disable the button and stop it appearing clickable $button.addClass('disabled').removeAttr('href'); } function enableFormsButton($button) { // Enable the button and make it clickable again $button.removeClass('disabled').attr('href', '#'); } 2. Below is the script.js that’s needed for CAPTCHA customization: /* * Custom credential handler for Google ReCaptcha. * If a credential of type "nf-recaptcha" is sent in any factor, this code * will be executed. The "submit" button of the form will be disabled * by default until the CAPTCHA is completed correctly. * * Use with the below WebAuth action: * add authentication webAuthAction recaptcha -serverIP <IP Address of google.com> -serverPort 443 -fullReqExpr q{"POST /recaptcha/api/siteverify HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Content-Type: applica tion/x-www-form-urlencoded\r\n" + "Content-Length: 10000"+"\r\n\r\n" + "secret=<Secret key from Google Recaptcha>&response=" + http.req.body(10000).after_str("&recaptcha=")} -scheme https -successRule "http.RES.body(1000).REGEX_MATCH(re/ \"success\": true.*\"hostname\": \"<FQDN of the Gateway/Auth vServer>\"/)" * */ CTXS.ExtensionAPI.addCustomCredentialHandler({ getCredentialTypeName: function () { return "nf-recaptcha"; }, getCredentialTypeMarkup: function (requirements) { var reCaptchaSiteKey = "XXXXX"; //Replace this with the SiteKey from Google ReCaptcha var div = $("<div></div>"); var CAPTCHA = $('<div id="g-recaptcha" class="g-recaptcha" data-callback="captchaSuccess"></div>').attr("data-sitekey", reCaptchaSiteKey); var input = $('<input id="captchaResponse" name="recaptcha" type="hidden" value=""/>'); div.append(CAPTCHA, input); var script = document.createElement("script"); script.type = "text/javascript"; script.src = "https://www.google.com/recaptcha/api.js"; script.onerror = function(){ var $form = $(".credentialform")[0]; var fieldDiv = $('<div class="field CredentialTypenone"></div>'); var leftDiv = $('<div class="left"></div>'); var label = $('<div class="standaloneText label error" role="alert"></div>').text("Could not load Google ReCaptcha. Please contact your administrator."); leftDiv.append(label); fieldDiv.append(leftDiv); $($form).append(fieldDiv); }; $("head")[0].appendChild(script); $(document).on('ctxsformsauthenticationdisplayform', function (e, data) { var $form = data.authenticationElement.find('.credentialform'); var $CAPTCHA = $form.find('#g-recaptcha'); $loginButton = $form.find('#loginBtn'); if ($CAPTCHA.length && $loginButton.length) { // This is a CAPTCHA form // Disable the submit button by default disableFormsButton($loginButton); } }); return div; } }); /* End ReCaptcha code */ var $loginButton; function captchaSuccess(data) { $("#captchaResponse").val(data); enableFormsButton($loginButton); } function disableFormsButton($button) { // Disable the button and stop it appearing clickable $button.addClass('disabled').removeAttr('href'); } function enableFormsButton($button) { // Enable the button and make it clickable again $button.removeClass('disabled').attr('href', '#'); }
3. nFactor for Gateway
If captcha is desired to work with Gateway vserver, then following commands can be used to leverage nFactor. Please note that all the existing commands above are needed, and they should not be changed. Only change needed is that the FQDN that is registered should now resolve to Gateway vserver.All the other commands remain unchanged.