Citrix NetScaler – OAuth to Azure AD with login_hint Subject Field

Reading Time: 6 minutes

Overview

What’s the biggest difference when choosing SAML instead of OAuth as the protocol when using Azure AD as IdP for NetScaler when it comes to User Experience (UX)?

You should consider this Question when you’re NOT doing an instant redirect to the IdP, instead doing first a Group Extraction (NoAuth) with eg OnlyUsername loginschema on the NetScaler side for filter mechanisms.

Your Users will enter their UPN / email on the NetScaler loginschema, followed by a second time after the redirect to Azure AD took place.

I stumbled first across this issue in my own Thread on Citrix Discussions and here also.

The restriction is based on Azure AD’s SAML flow. Spec-wise, login_hint is a subject field in SAML authN request. It’s only supporting a maximum of domain_hint, see Home Realm Discovery policy – Microsoft Entra | Microsoft Learn However, Azure AD does not support parsing out user_hint / login_hint from subject claim in the request.

So, as of now, Azure AD can use login_hint only when OIDC/OAuth is used and that’s enabled by default. Here’s what the redirected Browser-URL looks like when I first entered my UPN to the NetScaler loginschema with enabled OAuth:

Let’s trash the SAML Enterprise Application and create an App Registration with OAuth!

Configuration

Azure AD

Go to Azure AD, App registrations, start with a new registration and enter your public FQDN of Gateway / AAA vServer:

Azure AD new App registration

After creation, click on Endpoints and copy the authorization and token endpoint URL:

OAuth endpoints (v2)

Go on with Certificates & secrets, create a new Client secret and choose your preferred lifetime

Client secret

Client secret values cannot be viewed, except for immediately after creation. Be sure to save the secret value when created before leaving the page.

Copy Secret Value

Now switch to Enterprise applications and you will notice there’s the same name of your previously created App registration. Set your preferred settings like if an Assignment is required and assign Users or Groups. Assign at a minimum one Administrator and sign in later first with that Account, so you can consent on behalf of your organization and no User will get that notification:

OAuth App AAD Permissions

NetScaler

For OAuth, you have to use an AAA vServer, you can’t bind an OAuth Policy to a Gateway vServer directly.

Go to OAuth Action and create a new one:

OAuth NetScaler Action

Insert your previously copied values:

OAuth NetScaler Action Settings

Cert Endpoint isn’t mandatory but you should configure this to get a successful verification of JWT, which includes configured Attributes & Claims in Azure AD. The URL is https://login.microsoftonline.com/yourazureadid/discovery/v2.0/keys – see also my Script below.

Logfile when there’s no Cert Endpoint configured: (no parsing of the JWT token, no Claims inserted, so username is Anonymous)

Mar 27 11:16:26 <local0.info> 10.10.10.70 03/27/2023:09:16:26 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8508389 0 :  "OAUTH RP: idtoken length 1303, access token length 2134, certendpoint len 0, conf-keys len 0" 
Mar 27 11:16:26 <local0.info> 10.10.10.70 03/27/2023:09:16:26 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8508390 0 :  "OAUTH RP: Successfully verified incoming token/code, username: <Anonymous>, client ip 0xd4f0cf4f"

Logfile when there’s Cert Endpoint configured: (successful parsing of the JWT token and all Claims)

Mar 27 11:13:43 <local0.info> 10.10.10.70 03/27/2023:09:13:43 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8506628 0 :  "OAUTH RP: idtoken length 1095, access token length 2123, certendpoint len 90, conf-keys len 0" 
Mar 27 11:13:43 <local0.info> 10.10.10.70 03/27/2023:09:13:43 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8506629 0 :  "OAUTH: JWT: Successfully verified signature on JWT using the algorithm RS256" 
Mar 27 11:13:43 <local0.info> 10.10.10.70 03/27/2023:09:13:43 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8506630 0 :  "OAUTH: successfully verified JWT; Parsing the result {"aud":"80cd3e19-9e08-4aac-a292-a58f974854d7","iss":"https://login.microsoftonline.com/XXXXXXXX-41b743f0fe0c/v2.0","iat":1679907903,"nbf":1679907903,"exp":1679911803,"name":"Julian Jakob","oid":"2c4f5c70-XXXXXXX","preferred_username":"julian@hdxlab.com","rh":"0.ATwAq4xCC9IcxkG7bEG3Q_D-DBk-zYAInqxKopKlj5dIVNc8AAw.","sub":"n_6x2KxQWOiCdEylWzRcDcL9BQy5uhKm90w51TwCkXQ","tid":"0b428cab-1cd2-41c6-bb6c-41b743f0fe0c","uti":"eEvEESUmI06FLykrqghBAA","ver":"2.0"}" 
Mar 27 11:13:43 <local0.info> 10.10.10.70 03/27/2023:09:13:43 GMT HTZ-ADC02 0-PPE-0 : default AAATM Message 8506631 0 :  "OAUTH RP: Successfully verified incoming token/code, username: <julian@hdxlab.com>, client ip 0xd4f0cf4f" 

After the creation of an OAuth action, make sure the status switched to COMPLETE, which identifies the NetScaler was able to fetch a certificate from the AAD Cert Endpoint:

OAuth Action Status

Attributes & Claims

For additional Claims to be send, you have to enable the issuing setting, otherwise your configured claims will never reach NetScaler:

Enable Claims to be send

User

By default, the following claims will reach your NetScaler after successful authentication on Azure AD:

  • name (Julian Jakob)
  • oid (Azure AD object identifier attribute)
  • preferred_username (julian@hdxlab.com)

Which is than transferred to the following NetScaler attributes:

  • ssoDomain (hdxlab.com)
  • ssoUsername (julian@hdxlab.com)
  • ssoUsername2 (julian@hdxlab.com)

at a minimum, you should configure preferred_username as the User Name Field in the OAuth Server, otherwise your Session on NetScaler / StoreFront will be translated to “Anonymous”

NetScaler User Name Field for SSO

Following is a summary of possible claims if you’re needing more than the default is providing:

Optional User Claims Azure AD

Groups

The same goes to Group claims:

Group Claims in Azure AD

The group-membership claim is only transferring OnPrem Synced groups. If you need additional membership of Cloud groups, which makes sense when NetScaler is IdP, too (for example OAuth to Citrix DaaS as IdP) you have to set the types to Groups assigned to the application (and make sure all required Cloud + Synced groups to be read from are directly assigned to the corresponding Enterprise Application (NOT the App Registration itself))

Cloud Groups Claim

First testing you will notice the Cloud groups claim on NetScaler looks like this “groups”:[“f89d6013-9cc5-4918-b288-c84e6ede9063” – jep, that’s the Object ID.

To show the Display Name, edit the JSON and add ,”cloud_displayname” to the additionalProperties of the groups claim underneath idToken:

Enable Cloud Groups Display Name in Manifest

Script

Simple Script for creating a OAuth Action connecting to Azure AD with a LDAPS Group Extraction (NoAuth) in front of.

#Creating OAuth Action linked to Azure AD with preferred_username extraction for SSO to NetScaler / StoreFront and some additional useful attribute claims
#Binding to an AAA vServer with first doing LDAPS Group Extraction (NoAuth)
#Replace yourazureadid with your Azure AD Tenant ID
#Replace yourclientid with your Application (client) ID
#Replace yourclientsecret with your Client secret value

add authentication OAuthAction AuthAct_OAuth_AAD -authorizationEndpoint "https://login.microsoftonline.com/yourazureadid/oauth2/v2.0/authorize" -tokenEndpoint "https://login.microsoftonline.com/yourazureadid/oauth2/v2.0/token" -clientID yourclientid -clientSecret yourclientsecret -CertEndpoint "https://login.microsoftonline.com/yourazureadid/discovery/v2.0/keys" -userNameField preferred_username -allowedAlgorithms HS256 RS256 RS512 -Attribute1 email -Attribute2 family_name -Attribute3 given_name -Attribute4 upn -Attribute5 groups 
add authentication Policy AuthPol_OAuth_AAD -rule true -action AuthAct_OAuth_AAD


bind authentication vserver AAA_vServer_OAuth -policy lschema_OnlyUsername -priority 100 -gotoPriorityExpression END
bind authentication vserver AAA_vServer_OAuth -policy AuthPol_LDAPS_NoAuth -priority 100 -nextFactor AuthPol_OAuth_AAD -gotoPriorityExpression NEXT

Summary

I hope this post gave you some useful configuration insights about Azure AD and NetScaler when doing OAuth and may help you decide on which authentication protocol (SAML vs OAuth) your next deployment is based on.

13 comments

  1. Hi Julian,
    thanks for this post. I’m testing it in my Testenvironment but status remains in INIT or CERTFETCH and doesn’t complete. May you give me some tips:-)
    In your script the priority is twice 100 shouldn’t it be different?

  2. I have this configured and seems to work as it should but after login I’m receiving the below message. When I swap back to SAML the login works and Storefront is displayed.

    Citrix Gateway
    Citrix Gateway Plug-in for Java is not supported. For further help or information, contact your help desk or system administrator.

    Any ideas?

  3. Hi Jullian,

    In our case we are not able to set consent on behalf of your organization and do the configuration over that notification that you are mentioning.

    Can you please share also App registrations -> AAA/GW -> API Permission -> Configured permissions page in your case?

    We tried to configure that according https://docs.netscaler.com/en-us/citrix-adc/current-release/aaa-tm/authentication-methods/oauth-authentication/citrix-adc-oauth-idp.html
    but we still get see The status of the OAuth Server is CERTFETCH. after configuring OAUTH in gateway.

    Thank you in advance

    1. Hi Jiri,

      sure, these are my API Permissions for the App Registration, all is MS-Graph:
      – email, Delegated, View Users email address, Admin consent required: No
      – profile, Delegated, View Users basic profile, Admin consent required: No
      – User.Read, Delegated, Sign in and read user profile, Admin consent required: No

      When OAuth Server is still on CERTFETCH, check if your NetScaler is able to do name resolution to public (login.microsoftonline.com) and to communicate via https to fetch the certificate from MS.

      1. Thanks a lot for sharing

        We have those API permissions in place.
        Im getting name resolution from DNS server so I get response for command:
        show dns addRec login.microsoftonline.com with like 8 Records and IP address.

        In our case we need to use proxy because of security. It looks like I have some issue to fetch the certificate from MS over proxy.
        Proxy is allowed based on source IP where source IP is Gateway SNIP to Microsoft pages

        when I try curl https://login.microsoftonline.com
        curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to login. microsoftonline.com:443

        but when you specify the configuration of the connection over our proxy server it connects
        curl -x : https://login.microsoftonline.com//discovery/v2.0/keys
        {“keys”:[{“kty”:”RSA”,”use”:”sig”,”kid”:”nO…

        I need to ensure that authentication server will communicate over the proxy in this case
        So I tried different setting on ADC for proxy but I didn’t have luck to get this working.
        Is there something special to set in this case related to proxy?

        Thanks for the tips

  4. I figured out the issue with the plug-in error. Had to define my default AAA group in the oAuth policy. Once I did that everything was working.

    Is there a way to pass the UPN to the oAuth policy. Trying to give customers the ability to enter SAM account or UPN on the username only MFA for login. I’m storing the UPN in the attribute when I do the LDAP extraction.

    Thanks.

    1. Hi Chuck,

      as you can see in the first screenshot, the login_hint=julian@hdxlab.com is the UPN which I entered in the first Username-Only Loginschema for LDAP group extraction on NetScaler. With OAuth, this is the default which gets prefilled at the Azure AD login.

  5. Hi Julian,

    Your summary in overview chapter is not correct. Azure AD supports login_hint but as a query parameter in request url.
    It is tricky to change the ADC saml request url to add login_hint (authetication rewrite action does not allow response body mofification where the url is stored) but it is possible (a bit insane login flow involving adhoc creating of saml policies with another complication cause by the fact that ADC pair is running in Azure) .
    I made such solution, for DaaS – ADC gateway as authentication, AAD MFA (passwordless sign-in), with session SSO without FAS.

  6. Sorry for the noob question, but can this be used multi-tenant? And if so how could it be locked down to specific tenants?

    TIA

    1. Sure, see in my script “Binding to an AAA vServer with first doing LDAPS Group Extraction (NoAuth)” you could do a filtering on LDAPS Groups (Group1 = OAuth Action1,…) or you’re checking and filtering on the UPN-Suffix like user1@domain1 is redirected to domain1 OAuth Action.

Leave a Reply

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