NetScaler Native OTP – Prevent Enrollment of Additional Devices Externally

Download NetScaler Native OTP Device Limit Guide:
Full Version (GUI) | Short Version (CLI)

With the introduction of NetScaler 12.0 (build 51.24 to be exact), Citrix enhanced the value of NetScaler Unified Gateway even more by embedding the native support for one-time password (OTP). Initially, the OTP mobile apps were provided by third-parties, for example, Google and Microsoft Authenticators, but recently Citrix added this support to their own Citrix SSO mobile app.

I will not describe the details and benefits of NetScaler’s native OTP in this article. You are welcome to read about the solution in the Citrix Blog Post NetScaler Unified Gateway Provides One Time Password (OTP), Natively or watch the YouTube video here. There are several very detailed guides available to help you understand and configure the feature, such as Carl Stalhood and George Spiers guides as well as Citrix’s own NetScaler One Time Password (OTP) Guide. In this blog post I will share with you how we can limit the number of enrolled devices to one.

We first got this question from one of our clients, but after that we heard it many times from partners, fellow Citrix experts and other clients. At Citrix Synergy 2018 in Anaheim we shared the solution with Citrix NetScaler Engineering Team – the creators of nFactor and OTP support. Several Citrix customers and partners asked for this during Synergy sessions, so finally (sorry for the delay, guys) I am publishing it here.

Initially, the number of devices that can be enrolled for OTP was not limited. This, to some extent, defeated the purpose of the Multi Factor Authentication with OTP, as any user could register an OTP device as long as the user’s credentials were known. Citrix will limit this in the next builds of 12.0 and 12.1 to five devices, but even this may be unacceptable to most clients. In their guides Carl and George recommend limiting the access to OTP Management to internal networks only by configuring the Login Schema Policy with CLIENT.IP.SRC.IN_SUBNET(subnet/mask) expression:
http.req.cookie.value(“NSC_TASS”).eq(“manageotp“) && client.IP.SRC.IN_SUBNET(10.2.0.0/16)

We decided to take this further:

  • Prevent enrollment of any additional devices externally. This means the user can enroll OTP device(s) if he has not enrolled any devices yet. However, we will prevent device enrollment from external networks if the user has previously enrolled an OTP device.
  • Do not limit the number of devices that can be enrolled from internal networks.

OTP stores device enrollment secrets in an Active Directory attribute that accepts Strings. Citrix’s documentation uses the userParameters Active Directory attribute. This guide assumes that you are using the standard (recommended) userParameters AD attribute.

Once an OTP device is enrolled/registered, NetScaler will write a string into the userParameters attribute of the user in the following format:

#@DeviceName=AppID

The trick is to take the userParameters attribute and write it to NetScaler’s internal User Attribute (I will use Attribute #7 in this guide), so it can later be used in the Login Schema and Authentication Policy expressions to evaluate if the user had already enrolled a device.

Since we have to know whether or not NetScaler should display the second (OTP) password field after the LDAP authentication happens, we will modify the way we present the credential prompts to users. To sum it up, instead of this:

We will do this:
Step1

Step 2

As a bonus this method gives us more flexibility to check other conditions before deciding to prompt the user for the second authentication factor. For example, check the AD Group membership. Here is an example scenario where the company security policy requirements are as follows:

  • Regular users should not be prompted for 2FA to access the Unified Gateway
  • Privileged (SSL VPN or say RDP Proxy) users must be prompted for 2FA to get access

In this case, we can check the user’s AD Group membership before displaying the Authenticator Passcode prompt.

Scenario

We are going to build an authentication flow that includes the native OTP functionality. Here is the list of requirements:

  • The solution will support both single and multi-factor authentication
  • Only members of CTX-NetScaler-OTP AD Group will have the ability to use multi-factor authentication and enroll their devices
  • Users that are not members of CTX-NetScaler-OTP AD Group will be able to login with single-factor (LDAP) and have limited access to the Unified Gateway resources (managed by Session Policies, filtered by the AD Group)
  • Users will be limited to enroll a single device if accessing the Unified Gateway externally

Authentication Logical Flow and nFactor Policies

You can download the Short (CLI) Version of the guide or Full (GUI) Version that also includes some other considerations, such as Active Directory and Service Accounts.

Additional tip:
Check out How Do I Citrix NetScaler CLI series and grab a NetScaler CLI Troubleshooting cheat sheet to help you with your configurations.  It’s a handy cheat sheet that contains important commands, paths, and shortcuts, that are available on the net, but it usually takes way too much time to find them.

Please feel free to leave your comments and suggestions here, contact me via LinkedIn, email or follow me on Twitter.

By Stan Demburg


Explore how iRangers are changing the game by designing Virtualization technology solutions adjusted to real business needs.

For latest updates, follow us on LinkedIn.


29 thoughts on “NetScaler Native OTP – Prevent Enrollment of Additional Devices Externally

  1. My goal on this was to have 2Fa only enabled for users that were a member of an AD group. If they weren’t you would log in the first page with username and password, then get in to your citrix desktops. If you were a member of the group, the next page after login would be to enter your 2Factor key. I followed these instructions to a T, but they seem to get fuzzy towards the end of dealing with the group memberships and creating the session policies for them. After creating the two session policies, I went to bind the RDP.. one to the NS-Internal… AAA group, but I get an error:

    “Advanced VPN Session Policy cannot be bound if Classic VPN Session Policy is already bound to any entity (i.e. aaa user, aaa group, vpn vserver, vpn global)”

    Don’t understand that, as these are new policies that are just created and are not bound to anything. But even beyond that, I don’t see anywhere where you would map an AD account to this netscaler group, which is what I thought was part of this article. What I did instead, was in the authentication policy put my AD group in for AAA.USER.IS_MEMBER_OF() (and I used AAA.Users because netscaler said that HTTP.REQ.USERS was deprecated).

    When I go to the site, I get the login, but when I log in it goes to the next page where I get “No Active Policy During Authentication” error. I guess I’m stuck on where to go next. Any help would be great. Thanks

    1. Hi Ryan,
      The reason you’re getting the “Advanced VPN Session Policy cannot be bound if Classic VPN Session Policy is already bound…” error is because you have a Classic Session Policy bound somewhere else (ex. directly to your Gateway vServer, globally or AAA Group).

      In general, my advice is to create Advanced Session Policies, then unbind all your Classic Session Policies from everywhere and bind the new Advanced policies. Chances are you using simple “NS_TRUE” classic expressions which translate to “TRUE” advanced expression. The only valid reason to keep the Classic Policies is if you are using EPA checks as part of your Session Policies. In this case, you can migrate EPA to nFactor and get rid of Classic Policies (this still has some limitations) or keep the Classic Policies. If you want to keep Classic Policies, then use “NS_TRUE” classic expression instead of the “TRUE” advanced one.

      The AD Group membership check actually happens during the Authentication (which is before any Session Policy gets applied), after the LDAP Factor and before any of the OTP factors. It is done in the “UG-noauth-OTP-Verify_authpol” authentication policy:

      add authentication Policy UG-noauth-OTP-Verify_authpol -rule “AAA.USER.IS_MEMBER_OF(\”CTX-NetScaler-OTP\“) && HTTP.REQ.USER.ATTRIBUTE(7).CONTAINS(\”#@\”)” -action NO_AUTHN

      Replaced HTTP.REQ.USER. by AAA.USER – as you mentioned, the first one is deprecated.

      Cheers,
      Stan Demburg

  2. This is excellent design. I probably would spent weeks getting to that point on my own if ever.
    I have implemented it in my lab and it works as describe with few issues. I will work on them later.
    But maybe you can advise me how to do small change. For me all users are external, so I can’t block /manageotp once user added a device. What I want to do is next.
    1) if user does not have OTP registered just let him into /manageotp to add device (as you did)
    2) if user registered let him into /manageotp but ask OTP password before as well.
    I did next. In UG-noauth-OTP-Mgmt_authpol I just left: HTTP.REQ.COOKIE.VALUE(“NSC_TASS”).EQ(“manageotp”)
    Now any user can get in. Now I just need to add UG-noauth-OTP-Verify_authpol which has next condition AAA.USER.ATTRIBUTE(7).CONTAINS(“#@”) into the flow and enable Verification propmt.
    May be you can help me with advise how to add this into the flow.
    Now if user want to add new devise he/she will need to have old one. If old one not available they can call support and support will clear OTP filed in AD.

  3. I got a bit further but now completely stack.
    1) I have kept everything like you describe
    2) I have added another Policy UG-noauth-OTP-Mgmt-Verify_authpol and put condition /manageotp && AAA.USER.ATTRIBUTE(7).CONTAINS(“#@”), set to NO_AUTHN and set second factor to new policy label UG-OTP-Mgmt-Passcode-Verify_pollabel
    3) This label has own Schema (same as OTP-Passcode-Only.xml but with different text so I know I landed on right policy)
    It has new policy UG-LDAP-OTP-Mgmt-Verify-NoAuth_auth with true and your action LDAP-OTP-Verify-NoAuth_authsrv
    4) I have added this new policy to main Policy Label with weight 150 (before regular OTP)
    Now I have 2 issues:
    1) check HTTP.REQ.COOKIE.VALUE(“NSC_TASS”).EQ(“manageotp”) looks like not working all the time. Sometimes it completely ignores that I put [Link deleted]and follows main policy (piority 300) sometimes it hit policy even when I don’t put /manageotp in URL. Anyone has this issue?
    2) issue two even when I hit this new policy label. I see my custom Login Schema (and in logs). I put OPT but instead of /manageotp page I am redirected to StoreFront application page. What is going on?
    How reliably end up on OTP Managment page?
    I will try open case with Support, but I am afraid they might not be able to support advance configuration.
    I would appreciate if you have any ideas.

    1. I think problem is in this extra LoginSchema profile. If it has custom UI to ask OTP it redirects to Store Front, if I put LSCHEMA_INT it redirect to manageOTP without OTP verification.
      Do I need something special into this custom .xml file to redirect it to manageOTP to make it work?
      It looks like as soon as OTP verification done Netscaler ignores NSC_TASS and goes to ICA StoreFront.
      Or I am missing something here.

  4. Hi Leanid,
    The issue may not be in the Login Schema. The OTP-Verify custom Login Schema does not have any redirects.
    From what I see the issue is with NetScaler native OTP module. Once the OTP module is triggered, you can no longer get back to it. So, when OTP is verified (module triggered), you cannot go back to OTP device registration. The only way I could think this may work is if you chain another Policy Label (with LSCHEMA_INT) as a next factor for the OTP verification (LDAP) policy. So, it will look like this:

    Start-LDAPs_authpol -> (next factor) -> Single-or-2FA_pollabel -> UG-noauth-OTP-Verify_authpol -> (next factor) -> UG-OTP-2FA-Vefrify_pollabel -> (new) UG-LDAP-OTP-Verify-then-Manage-NoAuth_authpol (AAA.USER.ATTRIBUTE(7).CONTAINS(“#@”).NOT) -> (next factor – new) -> UG-OTP-Mgmt-NoSchema_pollabel (with LSCHEMA_INT) -> UG-LDAP-OTP-Mgmt-NoAuth_authpol -> END

    Let me know if this works for you. Sorry, I have no ability to test this right now.

    1. Wow. Your idea worked. Thanks a lot Stan.
      I have created Policy Label with my OTP verification for manageotp and bind auth policy with second factor to another Policy label without LoginShema (LSCHEMA_INT) with same authentication action and no second factor.
      It worked great. User sent to manageotp site!!! And I have protected manageotp site. .

      Now the only issue is NSC_TASS cookie. If user first logged in to StoreFront then logout and does not close browser click to https://mysite/manageotp it goes back to StoreFront. It does not properly set NSC_TASS cookie so ManageOTP policies do not get triggered and it follows regular nFactor path. If user closes browser and tries again everything works. I think this issue should affect everyone, since it has nothing to do with my policies. Is there some trick to clear cookies on logout or maybe at some other moment to make stable NSC_TASS detection?

      On a side note, I think I started to figure out this nFactor authentication. This is bloody complicated. Once you get it , it does not look that scary, but good luck someone from outside trying to figure it out.

      1. Stan,
        Just want to say Thank you for this article.
        With it’s help I was able to configure nFactor with native OTP protecting main site as well as ManageOtp, Forgot Password (password reset) and KBA subscription. That allows me to use it for external users.
        I have seen a lot of examples with GUI and CLI and it was really hard to understand all object connections, especially if you want to modify and extend it. Your diagram on top of this article was most useful peace of information that allowed me to understand nFactor. It still took me 3 days to extend OTP to KBA and ManageOTP, but at least now, I think, I know how it works.
        I still need to sort out reset of NSC_TASS cookie in some cases, but I will probably do it with some kind of custom java scripting.

        1. You’re welcome, Leanid!

          In regards to the NSC_TASS cookie issue you’re having, for one of my clients I configured a separate AAA Virtual Server for OTP device enrollment (AAA works exactly the same as Gateway). So, it this case users have to go to manage2fa.domain.com instead of gateway.domain.com/manageotp. The request is then redirected (with a Responder action) to manage2fa.domain.com/manageotp. As part of the Responder action, you can wipe the existing NSC_TASS cookie, so your Responder expression will be similar to this:

          “https://manage2fa.domain.com/manageotp”+”\r\n”+”Set-Cookie: NSC_TASS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\r\n\r\n”

          This will trigger NetScaler to generate a new NSC_TASS.

          1. Stan,
            Interesting approach. I will try it. I will send KBA registration there as well and keep “Password Reset” on Gateway site. From what I see “Password reset” is not using NSC_TASS cookie.
            Thanks a lot.

          2. Stan,
            This did not worked very well. It worked in principal, bit not the best user experience. User get redirected to different site and it does work for manageotp but there is no nice way back. Adding extra link “Go back” looks ugly. Also same approach does not work for KBA registration, since it want to redirect to main site (storefront) after it completed.
            I managed to catch NSC_TASS cookie when it come back from StoreFront logout (check referrer in header) and reset NSC_TASS. That helped a bit. Now the only user trap I have is inside manageotp and KBA registration. Once user in their flow there is no way back but finish it or restart browser. It desperately needed Cancel button. I can add button, but need to link it with some java script. I made it to call CTXS.Environment.logOff(); but it is not enough. I guess this is something I need to talk to Citrix support.

          3. Hi Leanid,

            For the Cancel button, you can try adding a redirect to “/cgi/tmlogout”. This is a standard NetScaler logout URL.

          4. I finally added Cancel button to forms. It was a pain.
            1) Had to register custom class with input type “submit” to prevent default submit action. Also had to add custom javascript first to set my own NSC_TASS_CLEAR cookie. I could not just clear NSC_TASS cookie since it has HttpOnly and not available for javascript, plus /cgi/tmlogout does not reset this cookie as well probably for the same reason.
            1) This is extension in script.js
            CTXS.ExtensionAPI.addCustomCredentialHandler({
            getCredentialTypeName: function () { return “nsg_cancel”; },
            getCredentialTypeMarkup: function (requirements) {
            var div = $(“”);

            $(document).on(‘ctxsformsauthenticationdisplayform’, function (e, data) {
            $(‘#nsg_cancel’).click(function() {
            document.cookie = “NSC_TASS_CLEAR=yes;path=/;Secure”;
            window.location.href = “/cgi/tmlogout”;
            return false;
            })
            });
            2) In style.css added css to make button flat:
            .CredentialTypensg_cancel input[type=”submit”] {
            display: inline-block;
            color: white;
            box-sizing: border-box;
            background-color: #999999;
            border: none;
            border-radius: 2px;
            transition:background-color 0.5s ease;
            cursor: pointer;
            }
            .CredentialTypensg_cancel input[type=”submit”]:hover {
            background-color: #777777;
            }
            3. Now in my custom Loginschima.xml added
            nsg_cancelnsg_cancel<Labelnone
            4. At Netscaler added NSC_TASS cookie cleanup if I detect my NSC_TASS_CLEAN cookie. Because of HttpOnly flag I had to do it with Netscaler policy and not javascript:
            “Set-Cookie: NSC_TASS=xyz;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\r\n”+”Set-Cookie: NSC_TASS_CLEAR=no;Path=/;expires=Wednesday, 09-Nov-1999 23:12:40 GMT;Secure\r\n\r\n”
            For policy condition:
            HTTP.REQ.HOSTNAME.contains(“mysite.com”) && HTTP.REQ.COOKIE.VALUE(“NSC_TASS_CLEAR”).EQ(“yes”)

            I will do more QA, but so far looks good. Again thanks a lot for your initial article it was good start point.

      2. Hi Leanid,
        Can you please share some CLI commands how you’ve fixed it?
        I’m trying to accomplish the same but i’m realy stock at the moment. My goal is to forward all the users to the manageOTP site if they don’t have any device configured.
        This is what you’ve done as well correct?
        Thank you very much!

        1. Jeor2011,
          Actually this is not what I do. And for your case my CLI will only confuse you. It already has KBA regitration and Password Reset with added OTP.
          What I do is what this Stan’s article does. Not enforcing OTP for users without registered device. At least at the beginning to allow soft rollout of this feature. So users without OTP device can login without issue. And I have added link to /manageotp to add device.
          Once they added device to OTP Stan’s example block access to /manageotp for external users. In my case I have added OTP verification to /manageotp if user already have a device registered. That was main deviation from this example and an issue that Stan help me to resolve.
          Users will need their old device to add a new one. If they don’t have it they will have to talk to support and support will zero out device registration in AD.

  5. Hello Stan,
    a great guide thank you! Today I’ve updated the Netscaler to the new version 13. Unfortunately, I can no longer register devices
    via /manageotp after the update. it looks as if you will be forwarded to the storefront after login via the URL /manageotp. it
    looks as if you will be forwarded to the storefront after login via the URL /manageotp.
    Do you have an idea?

    thx Philipp

    1. Hi Philipp,

      In Citrix ADC 13.0 Citrix slightly changed the OTP support. For example, native Push is now supported and you can set the max number of devices users can register.

      Your issue may be related to the max number of devices, which is 4 by default. You can adjust this in GUI:
      – Navigate to Security > AAA – Application Traffic, click Change authentication AAA OTP Parameter under Authentication Settings section.
      – On the Configure AAA OTP Parameter page, enter the values for Max OTP device Configured.

      Here is Citrix’s original documentation for the Native OTP on 13.0:
      https://docs.citrix.com/en-us/citrix-adc/13/aaa-tm/native-otp-authentication.html

      Let me know if adjusting the max # of devices helped.

      1. Hi Stan,
        many thanks for your response. I tried your trick with the maximum devices today, unfortunately without success. In addition, I have customized the “UG-noauth-OTP-Mgmt_authpol“ policy. But even with the change was not possible to add devices via the /manageotp URL. Do you have another idea? It almost looks like they have changed a lot under the hood with Version 13.

        thx
        Philipp

        1. Hi Philipp,

          Yes, it looks like lots of things have changed. However, /manageotp should still work.

  6. Hello Stan,
    Good Day!!
    Recently, I have upgraded my NS appliance to version12.1 build 51.19. I have the below requirements. Kindly suggest how can I achieve it:
    1) Can we restrict users to add 1 device in NS version12.1 build 51.19.
    2) I want to restrict users to add 1 device only whether they connected internally or external network
    3) Can I remove the option of Delete device from the OTP registration page.

    Kindly suggest the same.
    Awaiting your reply.
    Thanks.

    1. Hi Harsh,
      For #1 and 2 you can use the above instructions. This is exactly what they do – limit to a single device. For #2 to work just remove the internal IP range condition.

      In regards to #3, not sure how can this be achieved.

      1. Hi Stan,
        Thanks for the reply.
        I will try the same & will share the results.
        Thanks,
        Harsh Kumar

        1. Hi Stan,
          I have configured the same settings as described in the Article. However, when I try to login to ManageOTP site, it gives error “No Active policy during authentication”.
          Can you please suggest, where I am making mistake?

          Thanks.

        2. Hi Stan
          I have followed the same instructions as mentioned in the article. However, I made 1 mistake in between that while creating Authentication policylabel (UG-OTP-2FA-Verify_pollabel). I have assigned LSCHEMA_INT instead of “OTP-Passcode-Only_lschema”. Now when I am trying to delete that, it is saying “Resource in Use.” I am unable to delete that. I have also unbind the Policy but still the same.

          Can you please suggest how to fix it as when I am creating new policylabel for the same with other name, it does not take effect.

          Thanks.

  7. Hi Stan,
    I reconfigured the entire NetScaler again. Unfortunately with the same result. Have you been able to test anything in the lab environment?
    Thanks,
    Philipp

  8. Hi Stan,
    While configuring the Authentication PolicyLabel, I have created the PolicyLabel “UG-OTP-2FA-Verify_Pollabel” as LSCHEMA_INT. But now when I am trying to edit it or delete it, it says “Resource in use”
    Kindly suggest, how can I fix this.
    Thanks.

  9. Hi Stan,
    Thanks for the great article!!!
    Finally, I restored my NS to last restore & revert all the settings back to old one. Then, I followed the above instruction to T & able to achieve the same & keeping the device registration to single device. But its all done in Test environment.
    Now when am I try to integrate my storefront with new NS configuration, getting error : Cannot complete the request.
    Can you please provide some suggestions on the same?

    Thanks.

    1. Hi Harsh,

      There are quite a few reasons that could cause the “Cannot complete your request” error. Typically, this is caused by misconfiguration of the Storefront:
      – First, check if you can access and login to Storefront directly (no LB, no Gateway)
      – Check that your Gateway URL is configured right
      – If you configured the Callback URL, check that it is resolving to the NetScaler hosting your Gateway
      – Check out this Citrix KB: https://support.citrix.com/article/CTX207162

      Cheers,
      Stan

      1. Hi Stan,
        Thanks for the suggestion!!
        I created a new session policy & bind it with the NS VServer & I am in the business now.
        Thanks once again!!!

Leave a Reply

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