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:

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

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

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

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:

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:

Insert your previously copied values:

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:

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

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”

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

Groups
The same goes to Group claims:

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))

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:

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.
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?
Hi Simon,
is your NetScaler able to resolve and connect via https to https://login.microsoftonline.com/yourazureadid/discovery/v2.0/keys to fetch the certificate? The first 100 priority is binding a login schema, the second 100 priority is binding the authentication policy, so there are two different binding points.
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?
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
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.
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
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.
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.