Jump to content
Welcome to our new Citrix community!
  • Leveraging NetScaler nFactor for Seamless Integration with DUO via non-iframe RADIUS


    Juliano Reckziegel
    • Validation Status: Validated
      Summary: This article demonstrates how organizations can leverage NetScaler nFactor flexibility to seamlessly replace the expiring DUO iframe integration with non-iframe RADIUS integration, ensuring a smooth transition and continued security.
      Has Video?: No

    [UPDATE February 23, 2024] It has come to our attention that leveraging client IP information in DUO allows for the application of distinct policies, a feature previously overlooked in our integration. Specifically, we had failed to transmit this information to the DUO proxy server, thereby preventing the implementation of such policies. By default, DUO utilizes the RADIUS calling-station-id attribute as the client IP. In response, I have updated the NetScaler RADIUS action to include this attribute, ensuring that the client IP information is now accurately passed to the DUO proxy server. This enhancement enables the effective utilization of client IP data for policy application within DUO.

    As Cisco DUO's iframe integration reaches its end of support on September 30, 2024, organizations must transition to DUO Universal Prompt or adopt a RADIUS configuration without the iframe for continued support.

    In this article, we explore how to utilize the NetScaler nFactor framework to replicate the iframe functionality while leveraging the non-iframe RADIUS integration, ensuring a smooth transition for users and administrators alike.

    User Experience Overview:

    The integration facilitates a seamless user experience, ensuring secure authentication while maintaining convenience. Users are presented with a familiar interface where they can select authentication methods such as DUO Push, SMS, phone call, or passcode entry, ensuring flexibility and accessibility.

    The following image, depict the user experience using this integration method:

    Screenshot 2024-02-22 162033.png (3124×1569)

    How this works

    In addition to passcodes, DUO has special words that can be sent via RADIUS to the DUO proxy server that will trigger actions as followed:

    • push: Sends a push notification to the user's device.
    • sms: Sends an SMS with passcodes to the user's device.
    • phone: Initiates a phone call to the user's device for confirmation.
    • push#: Sends a push notification to the specified device (e.g., push2 for the second device).
    • sms#: Sends an SMS with passcodes to the specified device (e.g., sms3 for the third device).
    • phone#: Initiates a phone call to the specified device for confirmation (e.g., phone2 for the second device).

    NetScaler communicates with the DUO proxy server by sending the appropriate keyword based on the user's selection:

    • push: When the DUO Push option is selected.
    • passcode: When the user enters a passcode.
    • sms: When the Send SMS option is chosen.
    • phone: When the Call Phone option is selected.

    Users can manually select the passcode option and input special keywords (e.g., push2, phone3, sms4) to authenticate with alternative devices.

    Authentication Flow

    The authentication flow is configured with two factors:

    • DUO MFA via RADIUS: The first factor verifies the user's MFA against DUO via RADIUS.
    • LDAP Password: The second factor authenticates the user's password via LDAP.

    This sequential configuration helps to prevent that Active Directory (AD) accounts are locked due to excessive authentication attempts.

    DUO Configuration

    On the DUO proxy configuration file (authproxy.cfg), make sure you have a client session with [duo_only_client]  defined and create a new radius_server_duo_only entry on a port that is available, make sure to add the NSIP of both HA pair nodes and SNIP as RADIUS clients as needed.

    [duo_only_client]
    
    [radius_server_duo_only99]
    api_host=api-XXXXXXXX.duosecurity.com
    ikey=XXXXXXXXXXXXXXXXXXXX
    skey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    failmode=safe
    radius_ip_1=192.168.0.100
    radius_secret_1=RADIUS_SECRET_123
    radius_ip_2=192.168.0.151
    radius_secret_2=RADIUS_SECRET_123
    radius_ip_3=192.168.0.152
    radius_secret_3=RADIUS_SECRET_123
    port=18128
    

    Use this configuration as a reference for setting up the DUO proxy server in your environment. Make sure to adapt the settings to match your specific setup, including any changes in IP addresses, shared secrets, or port numbers. If further assistance is required, consult the DUO support documentation or reach out to their support team for guidance on configuring a new radius_server_duo_only session on the authproxy.cfg file.

    NetScaler Configuration

    Assuming that the Citrix Gateway virtual server is already provisioned with necessary configurations such as certificates, Secure Ticket Authority (STA) servers, and session policies/profiles, follow these additional steps to integrate Cisco DUO using the nFactor extensibility features:

    • Disable Single Sign-on Domain: Ensure that the "Single Sign-on Domain" is disabled on the session profile. This step is crucial for passing the UserPrincipalName to StoreFront.
    • nFactor Extensibility Features: Utilize the nFactor extensibility features to customize the authentication process and integrate Cisco DUO options seamlessly. This involves creating a new XML Schema and adding jQuery code to the portal theme scripts.js file.

    XML schema

    Create a new xml file called duo.xml located at /nsconfig/loginschema with the following content:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <AuthenticateResponse xmlns="http://citrix.com/authentication/response/1">
      <Status>success</Status>
      <Result>more-info</Result>
      <StateContext/>
      <AuthenticationRequirements>
        <PostBack>/nf/auth/doAuthentication.do</PostBack>
        <CancelPostBack>/nf/auth/doLogoff.do</CancelPostBack>
        <CancelButtonText>Cancel</CancelButtonText>
        <Requirements>
          <Requirement>
            <Credential><Type>none</Type></Credential>
            <Label><Text>dualauth_please_log_on</Text><Type>nsg-login-label</Type></Label>
            <Input/>
          </Requirement>
          <Requirement>
            <Credential><ID>login</ID><SaveID>login</SaveID><Type>username</Type></Credential>
            <Label><Text>dualauth_user_name</Text><Type>nsg-login-label</Type></Label>
            <Input><Text><ReadOnly>false</ReadOnly><InitialValue/><Constraint>.+</Constraint></Text></Input>
          </Requirement>
          <Requirement>
            <Credential><ID>passwd1</ID><SaveID>passwd1</SaveID><Type>password</Type></Credential>
            <Label><Text>dualauth_password</Text><Type>nsg-login-label</Type></Label>
            <Input><Text><Secret>true</Secret><Constraint>.+</Constraint></Text></Input>
          </Requirement>
          <Requirement>
            <Credential><ID>passwd2</ID><Type>duopasscode</Type></Credential>
            <Label><Text>dualauth_passcode</Text><Type>nsg-login-label</Type></Label>
            <Input/><!-- input field generated by custom handler -->
          </Requirement>
          <Requirement>
            <Credential><ID>passwd</ID><SaveID>passwd</SaveID><Type>duoselector</Type></Credential>
            <Label><Type>none</Type></Label>
            <Input/><!-- radio buttons created by custom handler -->
          </Requirement>
          <Requirement>
            <Credential><Type>duoinfotext</Type></Credential>
            <Label><Type>none</Type></Label>
            <Input/><!-- information message from custom handler -->
          </Requirement>
          <Requirement>
            <Credential><ID>Logon</ID><Type>none</Type></Credential>
            <Label><Type>none</Type></Label>
            <Input><Button>dualauth_submit</Button></Input>
          </Requirement>
        </Requirements>
      </AuthenticationRequirements>
    </AuthenticateResponse>

    Portal Theme

    Create a new portal theme called RfWebUI_DUO with the following NetScaler command:

    add vpn portaltheme RfWebUI_DUO -basetheme RfWebUI

    Add the following content to /var/netscaler/logon/themes/RfWebUI_DUO/script.js file:

    // This function creates the passcode field for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duopasscode"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var input = $("<input>");
        input.attr("id","passwd2");
        input.attr("name","passwd2");
        //input.attr("type","password");
        input.attr("type","text");
        input.attr("autocomplete","off");
        input.attr("spellcheck","false");
        input.prop("disabled",true);
        input.on("change",function(){$("#duocode").attr("value",$("#passwd2").val());});
    
        return input;
    
      }
    
    });
    
    // This function creates the radio buttons for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duoselector"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var table = $("<table></table>");
        table.addClass("radioButtons");
        table.css("border-spacing",0);
        table.css("height",44);
    
        var tr = $("<tr></tr>");
        tr.appendTo(table);
    
        var tdbase = $("<td></td>");
        tdbase.css("width","25%");
    
        var btbase = $("<input>");
        btbase.attr("type","radio");
        btbase.attr("name","passwd");
    
        var lbbase = $("<label></label>");
        lbbase.css("font-size","12px");
        lbbase.css("color","#999999");
    
        var button = btbase.clone();
        button.attr("value","push");
        button.attr("id","duopush");
        var label = lbbase.clone();
        label.attr("for","duopush");
        label.text(" Duo Push");
        var td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        button.prop("checked","checked");
    
        button = btbase.clone();
        button.attr("value","code");
        button.attr("id","duocode");
        label = lbbase.clone();
        label.attr("for","duocode");
        label.text(" Passcode");
        td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",false)});
    
        button = btbase.clone();
        button.attr("value","sms");
        button.attr("id","duotext");
        label = lbbase.clone();
        label.attr("for","duotext");
        label.text(" Send SMS");
        td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        button = btbase.clone();
        button.attr("value","phone");
        button.attr("id","duocall");
        label = lbbase.clone();
        label.attr("for","duocall");
        label.text(" Call Phone");
        td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        return table;
    
      }
    
    });
    
    // This function creates the information text for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duoinfotext"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var link = $("<a>here</a>");
        link.attr("href","https://support.fqdn.com.lab");
        link.attr("target","_blank");
        link.css("color","#02a1c1");
    
        var div = $("<div></div>");
        div.css("width",385);
        div.css("font-size","12px");
        div.css("color","#999999");
        div.css("text-align","center");
        div.append("Click ",link," if you are not enrolled in Duo.");
        div.append("<br>","For assistance call the Help Desk, (XXX) XXX-XXXX.");
    
        return div;
    
      }
    
    });

    NetScaler commands

    To the nFactor flow on NetScaler, you would use a series of CLI commands. Below are the commands to put together the configuration for integrating Cisco DUO using nFactor extensibility features and enabling/disabling the passcode field:

    Login Schema

    add authentication loginSchema DUO_LS -authenticationSchema "/nsconfig/loginschema/duo.xml"
    add authentication loginSchemaPolicy DUO_LSPOL -rule TRUE -action DUO_LS

    RADIUS policy/action

    In this particular step, the authTimeout is set to 60 seconds. This duration allows users a window of up to one minute to respond to authentication prompts such as push notifications or phone calls. Adjusting the authTimeout value provides flexibility in accommodating user response times within the nFactor authentication flow. The callingstationid parameter is used to pass the user source IP to the DUO proxy server.

    add authentication radiusAction DUO_SRV -serverIP 192.168.0.51 -serverPort 18128 -authTimeout 60 -radKey RADIUS_SECRET_123 -callingstationid ENABLED
    add authentication Policy DUO_POL -rule TRUE -action DUO_SRV

    LDAP policy/action

    In this step, the ssoNameAttribute parameter is configured to specify the userPrincipalName. As a result, there's no need to explicitly specify the Single Sign-on Domain on the session policy. This configuration streamlines the authentication process by automatically utilizing the userPrincipalName attribute for Single Sign-on purposes without additional policy settings.

    add authentication ldapAction LDAP_SRV -serverIP 192.168.0.5 -serverPort 636 -ldapBase "dc=company,dc=lab" -ldapBindDn serviceaccount@company.lab -ldapBindDnPassword SERVICE_ACCOUNT_PASSWORD -ldapLoginName sAMAccountName -groupAttrName memberOf -subAttributeName cn -secType SSL -ssoNameAttribute userprincipalname -passwdChange ENABLED
    add authentication Policy LDAP_POL -rule TRUE -action LDAP_SRV

    Policy Label

    This policy label represents the second factor of the authentication flow.

    add authentication policylabel LDAP_PL_noschema -loginSchema LSCHEMA_INT
    bind authentication policylabel LDAP_PL_noschema -policyName LDAP_POL -priority 100 -gotoPriorityExpression NEXT

    Authentication logic

    This authentication logic is implemented on an authentication virtual server.

    add authentication vserver DUO_AAA SSL 0.0.0.0
    bind authentication vserver DUO_AAA -policy DUO_LSPOL -priority 100 -gotoPriorityExpression END
    bind authentication vserver DUO_AAA -policy DUO_POL -priority 100 -nextFactor LDAP_PL_noschema -gotoPriorityExpression NEXT
    bind ssl vserver DUO_AAA -certkeyName Sample01_SAN

    The last command in this block binds a certificate to the authentication virtual server. This step is optional and primarily serves to display the virtual server in an UP (operational) state.

    Authentication Profile

    The authentication profile is used to link the authentication virtual server with a Citrix Gateway virtual server.

    add authentication authnProfile DUO_AuthProf -authnVsName DUO_AAA

    Citrix Gateway configuration

    As mentioned previously, we are considering and pre-existing Citrix Gateway virtual server, in this case it is called "gw214.company.lab DUO".

    The following command will link it to the authentication logic:

    set vpn vserver "gw214.company.lab DUO" -authnProfile DUO_AuthProf

    The following command binds the new RfWebUI theme to the Citrix Gateway virtual server.

    bind vpn vserver "gw214.company.lab DUO" -portaltheme RfWebUI_DUO

    No SMS option

    If your organization deems SMS as an unsuitable MFA method, you have the option to disable it within the authentication interface. This can be accomplished by adjusting the script.js file.

    Screenshot%202024-02-22%20183331.png

    To implement this change, follow these steps:

    • Modify the width of the table cell (td) to 33% to accommodate the remaining authentication options effectively.
    • Comment out the section of the code responsible for generating the SMS option.

    By making these adjustments, the SMS option will no longer be displayed within the authentication interface, ensuring that only the preferred MFA methods are presented to users during authentication.

    // This function creates the passcode field for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duopasscode"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var input = $("<input>");
        input.attr("id","passwd2");
        input.attr("name","passwd2");
        //input.attr("type","password");
        input.attr("type","text");
        input.attr("autocomplete","off");
        input.attr("spellcheck","false");
        input.prop("disabled",true);
        input.on("change",function(){$("#duocode").attr("value",$("#passwd2").val());});
    
        return input;
    
      }
    
    });
    
    // This function creates the radio buttons for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duoselector"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var table = $("<table></table>");
        table.addClass("radioButtons");
        table.css("border-spacing",0);
        table.css("height",44);
    
        var tr = $("<tr></tr>");
        tr.appendTo(table);
    
        var tdbase = $("<td></td>");
    //   tdbase.css("width","25%");
        tdbase.css("width","33%");
    
        var btbase = $("<input>");
        btbase.attr("type","radio");
        btbase.attr("name","passwd");
    
        var lbbase = $("<label></label>");
        lbbase.css("font-size","12px");
        lbbase.css("color","#999999");
    
        var button = btbase.clone();
        button.attr("value","push");
        button.attr("id","duopush");
        var label = lbbase.clone();
        label.attr("for","duopush");
        label.text(" Duo Push");
        var td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        button.prop("checked","checked");
    
        button = btbase.clone();
        button.attr("value","code");
        button.attr("id","duocode");
        label = lbbase.clone();
        label.attr("for","duocode");
        label.text(" Passcode");
        td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",false)});
    
    //    button = btbase.clone();
    //    button.attr("value","sms");
    //    button.attr("id","duotext");
    //    label = lbbase.clone();
    //    label.attr("for","duotext");
    //    label.text(" Send SMS");
    //    td = tdbase.clone();
    //    td.append(button,label);
    //    td.appendTo(tr);
    //    button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        button = btbase.clone();
        button.attr("value","phone");
        button.attr("id","duocall");
        label = lbbase.clone();
        label.attr("for","duocall");
        label.text(" Call Phone");
        td = tdbase.clone();
        td.append(button,label);
        td.appendTo(tr);
        button.on("change",function(){$("#passwd2").prop("disabled",true)});
    
        return table;
    
      }
    
    });
    
    // This function creates the information text for Duo
    
    CTXS.ExtensionAPI.addCustomCredentialHandler({
    
      // Credential type defined in login schema
      getCredentialTypeName: function() { return "duoinfotext"; },
    
      // Generate HTML for the custom credential
      getCredentialTypeMarkup: function(requirements) {
    
        var link = $("<a>here</a>");
        link.attr("href","https://support.fqdn.com.lab");
        link.attr("target","_blank");
        link.css("color","#02a1c1");
    
        var div = $("<div></div>");
        div.css("width",385);
        div.css("font-size","12px");
        div.css("color","#999999");
        div.css("text-align","center");
        div.append("Click ",link," if you are not enrolled in Duo.");
        div.append("<br>","For assistance call the Help Desk, (XXX) XXX-XXXX.");
    
        return div;
    
      }
    
    });

    Conclusion

    In conclusion, this article highlights the versatility of NetScaler nFactor in addressing the imminent discontinuation of the DUO iframe integration. By leveraging nFactor's capabilities, organizations can seamlessly transition to alternative authentication methods while maintaining security and a similar user experience.

    In this article, I've demonstrated a direct connection to the DUO RADIUS proxy and LDAP servers for simplicity. However, as a best practice, it's recommended to implement a load balancing virtual server for these components. This approach enhances the reliability and availability of the authentication infrastructure, ensuring seamless operation even in the event of server failures or high traffic loads.

    It's worth noting that users with multiple devices can conveniently authenticate by selecting the passcode option and inputting special keywords such as push#, sms#, phone# followed by the device number. For example, entering push2 will trigger a push notification to the user's second registered device.

    Credit for the original engineering of this solution goes to my former colleague, Edson da Luz, in 2021. I've made minor updates to present it here. Kudos to Edson for his creativity and skill in implementing this logic.

    This article serves as a testament to the adaptability and innovation within the cybersecurity landscape, offering practical solutions to evolving challenges in authentication and access management.


    User Feedback

    Recommended Comments

    I implemented this today, but when I try to use SMS notification, the logon page errors with "Incorrect Username or Password" after which i can choose Passcode and put in the code texted.  Is there a way for it to enable the passcode field after the SMS is sent without it displaying the error and forcing another entry of username & password?

    Link to comment
    Share on other sites

    15 hours ago, Mike Biracree said:

    I implemented this today, but when I try to use SMS notification, the logon page errors with "Incorrect Username or Password" after which i can choose Passcode and put in the code texted.  Is there a way for it to enable the passcode field after the SMS is sent without it displaying the error and forcing another entry of username & password?

    Hi Mike, Yes - you can customize the nFactor flow to suit your needs, also the order and requirements. And you can also adjust the flow based on what happend in the last action.

    Link to comment
    Share on other sites

    Hello,

    I am struggling to figure out to modify the XML and .js file to do configure the log in so it is push only?

    I can get the radio buttons and the passcode text box removed but I can't get the passcode line to fully go away?

    Any guidance is greatly appreciated!

     

    Thank you,

    Scott

    Link to comment
    Share on other sites

    3 hours ago, Scott Pauls1709162618 said:

    Hello,

    I am struggling to figure out to modify the XML and .js file to do configure the log in so it is push only?

    I can get the radio buttons and the passcode text box removed but I can't get the passcode line to fully go away?

    Any guidance is greatly appreciated!

     

    Thank you,

    Scott

    I was able to figure this out! I got it looking exactly like what I want / need 🙂

    The only other thing I just found out is with the configuration applied as above if you type your LDAP password wrong you still get the Duo Push prompt to approve or deny. If approved and you typed your password wrong it tells you that. How would we reverse it so it validates the LDAP password first and then does the radius / duo prompt?

     

    Thanks,

    Scott

    Link to comment
    Share on other sites

    On 2/28/2024 at 11:24 AM, Mike Biracree said:

    I implemented this today, but when I try to use SMS notification, the logon page errors with "Incorrect Username or Password" after which i can choose Passcode and put in the code texted.  Is there a way for it to enable the passcode field after the SMS is sent without it displaying the error and forcing another entry of username & password?

     

    This occurs because DUO responds with a RADIUS REJECT instead of a RADIUS CHALLENGE. Perhaps consulting a DUO SME could help determine if there's a configuration option in the DUO proxy to alter this behavior.

    Link to comment
    Share on other sites

    On 3/1/2024 at 3:09 PM, Scott Pauls1709162618 said:

    I was able to figure this out! I got it looking exactly like what I want / need 🙂

    The only other thing I just found out is with the configuration applied as above if you type your LDAP password wrong you still get the Duo Push prompt to approve or deny. If approved and you typed your password wrong it tells you that. How would we reverse it so it validates the LDAP password first and then does the radius / duo prompt?

     

    Thanks,

    Scott

    Hi Scott

    I am glad you were able to implement this solution.

    Evaluate DUO first was intentional to protect the user account against locks attacks. You can invert the authentication flow, but you will need to restructure the the authentication logic and edit the XML and script.js files as followed:

    XML:

    Change the following lines:

    <Credential><ID>passwd1</ID><SaveID>passwd1</SaveID><Type>password</Type></Credential>

    ..

    <Credential><ID>passwd</ID><SaveID>passwd</SaveID><Type>duoselector</Type></Credential>

    to:

    <Credential><ID>passwd</ID><SaveID>passwd</SaveID><Type>password</Type></Credential>

    ..

    <Credential><ID>passwd1</ID><SaveID>passwd1</SaveID><Type>duoselector</Type></Credential>

     

    script.js

    Change the following lines

    btbase.attr("name","passwd");

    to:

    btbase.attr("name","passwd1");

     

    I hope this helps you.

     

    Best Regards

    Link to comment
    Share on other sites



    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

×
×
  • Create New...