Walkthrough: MSAL.Net client calling Azure API App with Easy Auth enabled in a B2C tenant

In a previous blog post, I talked about how to use MSAL.Net client application to call an Azure Function App with Easy Auth enabled in a regular tenant. In this post, I’ll describe the process to use an MSAL.Net client application to call a Web API application with Easy Auth enabled in a B2C tenant. I assume you already have an API App created on Azure App Service. If you don’t have one, follow the instruction at https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-rest-api to create a sample API app and deploy to Azure App Service. Let’s go through this exercise:

  1. Register the API App in a B2C tenant

Log into your Azure AD B2C tenant -> Click on the ‘Azure AD B2C’ blade -> Applications -> Add to add a new application with the following setting:

Name Enter a name
Include web app / web API yes
Allow implicit flow yes
Reply URL https://<sitename>.azurewebsites.net/.auth/login/aad/callback
App ID URI <tenant>.onmicrosoft.com/demo or enter your own scope
Native client no

Note:
for the Reply URL, you will need to enter the URL in that format since the aad/callback endpoint gets processed by the Easy Auth module

Here is what mine looks like after this is done. My API App is hosted at https://bhwebapitest.azurewebsites.net. Take note of the Application ID as you will need it later to configure Easy Auth

Click on ‘Published scopes’ and take note of the full scope value since you will need this for the MSAL.Net application.

  1. Register an MSAL.Net client app in a B2C tenant

Follow the step above to create a new app registration with the following setting:

Name Enter the name of the client app
Include web app / web API No
Include native client yes

Once the application is created, click on API access -> Add -> select the API app created above in step 1 and select all available scopes and click on OK.

Here is what mine looks like after this is done. Take Note of the Application ID in the Properties blade as you will need it for the MSAL.Net application

  1. Create a new User flow policy in the B2C tenant

In the Azure AD B2C blade -> select User flows (policies) -> click on “New user flow” and create a new “Sign up and sign in” policy. Follow the wizard to provide a policy name and select any attribute you desire under “User attributes and claims” section.

Once the policy is created, select that policy and click on “Run user flow” button. Take note of the URL under “Run user flow” as you will need it to configure Easy Auth.

My policy in this case in this case is B2C_1_SiUpIn and the well-known configuration URL is https://hellob2c.b2clogin.com/tfp/hellob2c.onmicrosoft.com/B2C_1_SiUpIn/v2.0/.well-known/openid-configuration

Note that this URL is dependent upon the domain used in the “Select domain” drop down. I used the format b2clogin.com domain in this case. If you change this domain to login.microsoftonline.com, the well-known configuration URL will be changed accordingly to https://login.microsoftonline.com/tfp/hellob2c.onmicrosoft.com/B2C_1_SiUpIn/v2.0/.well-known/openid-configuration.

  1. Enable Easy Auth for the API Application

Follow the steps in the previous blog to turn on Easy Auth. Choose Advanced Mode instead of Express Mode for this. For ‘Client ID’ field enter the Application ID of the B2C App created in step 1 above and use the well-known URL in the “Run user flow” for the “Issuer Url” field (see screen shot below). Click OK and Save to save the setting. Test the Easy Auth configuration in the web browser by browsing to your web API URL to make sure it’s working correctly. You should be prompted for login. Use an account in the B2C tenant to verify you can log in and get to the API application.

  1. Create an MSAL.Net application

Create a C# Console Application with the following code. Make sure to add the MSAL.Net nuget package (Microsoft.Idenity.Client) to the application. You will need to change the following parameter for your application

// your b2c tenant name
private static readonly string Tenant = "<tenant>.onmicrosoft.com";
// your b2c tenant name
private static readonly string AzureAdB2CHostname = "<tenant>.b2clogin.com";
// Application ID of the MSAL.Net app
private static readonly string ClientId = "<Application ID>";
// Your Policy Name. Should be something similar to B2C_1_SomeName
public static string PolicySignUpSignIn = "<Your Policy Name>";
// use the full API scope in the "Published scopes" of the API app
public static string[] ApiScopes = { "<Your Web API scope>" };
// Your Web API
public static string ApiEndpoint = "<Your API endpoint>";

[gist id=”4e9f87996c88d65f26f247b381ea6505″ file=”MSALNetB2C.cs”]
Now you can test your MSAL.Net application to verify that it can successfully call the Web API application.

If you are using login.microsoftonline.com domain…

Just in case you are using login.microsoftonline.com domain instead of the b2clogin.com domain, in the Easy Auth ‘Issuer Url’ configuration you will need to enter the well-known configuration URL for login.microsoftonline.com as seen in the Azure AD B2C portal.

For the MSAL.Net application everything else is the same as above except for the AzureAdB2CHostname variable and it should be “login.microsoftonline.com”

private static readonly string AzureAdB2CHostname = “login.microsoftonline.com”;

References

https://cgillum.tech/2016/08/10/app-service-auth-and-azure-ad-b2c-part-2/

Receiving error AADSTS7000218: The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’

Problem:

An application receives the following error when authenticating to Azure Active Directory:

{
     “error”: “invalid_client”,

     “error_description”: “AADSTS7000218: The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’.\r\nTrace ID: xxx\r\nCorrelation ID: xxx\r\nTimestamp: 2019-08-18 20:38:28Z”,

     “error_codes”: [7000218],

     …

}

What does this error mean?

The error is what it said.  When authenticating to Azure AD to get an access token, the client application is not providing its “password” (in the form of either a client secret or a client assertion) as expected by Azure AD’s token endpoint.  Before diving into this error, let’s spend a few minutes understanding the context of the issue.  As defined in the OAuth2 specifications, there are 2 types of client applications:

  • Confidential Client – a client who is capable of storing a secret (used to authenticate to Azure AD).  An example of this client is a web application, where its code and secret are stored on the server that’s not exposed to the public.  The application’s confidential information can only be accessed by an admin person.
  • Public Client – as you guess,  a client not capable of storing any secret.  An example of a public client is a mobile application or a desktop application running at public kiosk in an insecure and unmanaged environment.

A confidential client is always expected to provide its credential (client secret or assertion) when authenticating to Azure AD.

So how does Azure AD know if the authenticating client is public or confidential?

In most cases, Azure AD looks at the reply / redirect URL provided in the request and cross check it with the reply URL registered in Azure AD’s App Registration blade find out the type (see screen shot below).  A ‘Web’ type is Confidential client and the ‘Public client (mobile & desktop)’ type is a Public client

In certain OAuth2 authentication flows such as OAuth2 resource owner password credentials (ROPC) grant flow, OAuth2 device code flow, and Integrated Windows Authentication, there is no reply URL provided in the token request.  In such case,  Azure AD looks at the app registration’s default type (see screen shot below) to determine if the client is confidential or public.

If “yes’ is selected, it’s a public client.  ‘No’ is a confidential client.

Yes in the current Azure AD’s App Registration model, a registered application can be both a public client and a confidential client, depending on the context the application is used in.  This is because an application may have part of it used as a public client while some other parts are designed to be used as a confidential client.  Depending on certain workflows the application developer must decide if the application should act as public or confidential client.

A Confidential client is expected in certain OAuth2 grant flows

Client Credentials flow, Authorization Code flow, and On-Behalf-Of flow are used by Confidential client to request a token.  Azure AD will return the above error if the request is missing a client secret or a client assertion.

How do I know which grant type and what redirect URL (if any) my application is using?

Review your application code or take a Fiddler trace to see what the ‘grant_type’ parameter and what ‘redirect_uri’ parameter are sent in the POST request to Azure AD’s token end point: https://login.microsoftonline.com/<tenant name>/oauth2/token (V1 endpoint) or https://login.microsoftonline.com/<tenant name>/oauth2/v2.0/token (V2 endpoint).

Refer to the table below for more information on grant type and oauth2 flow:

OAuth2 flow

grant_type value

ROPC

password

Device code

urn:ietf:params:oauth:grant-type:device_code

Authorization Code

authorization_code

Client Credentials

client_credentials

On-Behalf-Of

urn:ietf:params:oauth:grant-type:jwt-bearer

SAML Bearer Assertion

urn:ietf:params:oauth:grant-type:saml1_1-bearer

So what’s the resolution?

By now you probably figure this out that if the application is expected to be a Confidential client then provide the client secret or assertion.  In some authentication flow scenario like ROPC or Device Code flow where you don’t expect the client application to be confidential, follow the steps below to change the default type to Public client.

  1. Go to the App Registration for this application
  2. Select “Authentication” – > tick ‘Yes’ for Default client type -> Save the change

Reference

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-Applications

AADSTS5011 Error on Kubernetes container app running .Net Core code with an Ingress Controller

I recently worked with a customer who was receiving an AADSTS5011: The reply url specified in the request does not match the reply urls configured for the application on his .Net Core Kubernetes container application with an Ingress Controller. When running from the Docker environment, there was no issue however, the redirect URI was configured for http://localhost:someport . Once deployed, the redirect URI ( as seen in the address bar of the browser at the Azure login prompt ) was set to his application properly however, the context was HTTP and not HTTPS. This is a problem because, in Azure AD, you have to set the reply URL scheme to be https, except for localhost. The customer was using Microsoft.Identity.Web for authentication.

SSL Was being terminated at the controller

The first issue we discovered was that the controller was terminating SSL. We worked with a Kubernetes engineer to get the controller setup for SSL passthrough, setting up a SSL certificate for the application and still, the context was set to HTTP instead of HTTPS and now, the customer was experiencing an infinite number of redirects until it just timed out ( the new error was “…too many redirects” ).

Mircrosoft.Identity.Web not fully ready for GA

Even though the customer found the Microsoft.Identity.Web library on our GitHub, this library isn’t fully ready for GA and may not be anytime soon ( a fact that I hope has been noted on the GitHub by now ). However, we did finally resolve this last issue by simply specifying the Context.Request.Scheme to be “https” in code. This was presented from an ASP.NetCore engineer with this document: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1#forwarded-headers

Here is the code snippet where the final fix was done in the applications startup.cs file:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

{

    app.Use((context, next) =>

    {

        context.Request.Scheme = “https”;

        return next();

    });

    app.UseForwardedHeaders();

Conclusion

Even though the Microsoft.Identity.Web library isn’t fully GA ready yet and we experienced serious difficulties in the Kubernetes environment, a multi-faceted resolution was found for the problem. Hopefully, this blog post makes the experience less painful for the next developer!

Walkthrough: Using MSAL.Net to call an Azure Function App with Easy Auth enabled

In this post, I’ll walk through the steps for how to create an application using Microsoft Authentication Library for .Net (MSAL.Net) to call an Azure AD protected Azure Function App using Easy Auth (Azure App Service’ Authentication and Authorization feature).

1. Create an Azure Function with Easy Auth enabled:

Assuming you already have an Azure Function App created (refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal if you don’t know how to create one), go to that Function App in the Azure portal and create an Azure Function. For convenience, I’ll use the Http Trigger In-portal template to create an Azure Function.

Click on ‘Continue’ and then select ‘Webhook + API’ blade and then ‘Create’.

The template generates the following code in the run.csx file

#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");

string name = req.Query["name"];

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;

return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

Now that we have our Function created, we need to turn on Easy Auth, so navigate to the Azure Function App and select the “Authentication / Authorization” link in the ‘Platform features’ tab

 

Select ‘On’ for App Service Authentication and ‘Log in with Azure Active Directory’ in the “Action to take when request is not authenticated” section

 

 

In the ‘Authentication Providers’ section select ‘Azure Active Directory’ and choose the Express for Management mode and ‘Create New AD App’ and Save:

Now that Easy Auth is turned on, test the Function App URL in the browser to make sure it requires authentication. The Function URL can be obtained from the “</> Get function URL” link. Append the name query string at the end since the Function expects this name value. My test Function URL is https://easyauthfunctest.azurewebsites.net/api/HttpTrigger1?code=zIrQXHC9ypU2ewa4YadZfXgka7XgNG/U7J/kDGW79aXig3q907jo2A==&name=Azure

 

When I navigate to the above link, I get prompted for credential and then see the message “Hello, Azure” in the browser after logging in.

 

 

2. Create a new App Registration for the MSAL .Net app

Navigate to the Azure portal -> Azure Active Directory -> App registrations -> New registration to create a new App Registration. I use the following parameter for my registration.

Name

msalclient

Supported account types

Accounts in this organizational directory only

Redirect URI

myapp://auth

Once created, go to that app registration select API permissions -> Add a permission -> select the above Azure Function App in the ‘APIs my organization uses’ tab to add that permission:

 

Take note of the following since you will need them later when creating the MSAL App.

 

Overview blade:

  • Application ID
  • Directory ID

Authentication blade:

  • Redirect URI – this should be myapp://auth
    as registered above

API permissions blade:

  • Get the scope for this Azure Function App: (see screen shot below):

 

3. Create an MSAL Application

  1. In Visual Studio, create a new C# Console (.Net Framework) project
  2. Add the “Microsoft.Identity.Client” nuget package to the project

  3. Use the following code in the Program.cs file. Remember to fill in the scopes, ClientId, replyURL, and Tenant (Directory ID) with the info above

 

class Program
{
static string[] scopes = new[] { "https://easyauthfunctest.azurewebsites.net/user_impersonation" };
static string ClientId = "xxx";
static string Tenant = "xxx";
static string replyURL = "myapp://auth";
…
}

[gist id=”9f03df153b77e7cf78041f3fba4880f5″ file=”Program.cs”]

  1. Build and run the application to login to Azure AD. Once logged in you should see the following output in the console

Voila, we got the output from Azure Function App.

Infinite redirect between OpenID Connect Application and Azure AD

Recently I came across an interesting infinite redirection problem between an OpenID Connect (OIDC) Application and Azure AD as demonstrated in the Fiddler screen shot below. After authentication to Azure AD, we are stuck in an infinite loop between the web site and Azure AD. Eventually the browser gives up and throws an error. This problem only occurs if I start browsing to the site by http protocol (frame 3). If I use https, everything works fine.

Looking at the above Fiddler trace, what I notice unusual about this trace is that the problem starts at frame 16. The response in this frame should be a 200 OK instead of a 302 redirect back to Azure AD. This is the starting point for the infinite loop.

Let’s dive into the analysis

Looking at frame 15 below, we POST the id token to the site. After validating the token is good, the application invalidates the nonce cookie by setting its ‘expires’ attribute to a time in the past – Jan 1970 (expected). The application then sets up an authenticated session with the client by setting the “.AspNet.Cookies” cookie. Note there is a ‘secure‘ attribute on this cookie, which means that the cookie is only sent for secure https request. The POST request results in an HTTP redirect back to the site. See the problem? The redirection happens over http because this is the original scheme used to browse to the site (frame 3).

Hover over image to enlarge

In frame 16 the browser sends an http request back to the site. This request lacks the authentication “.AspNet.Cookies” cookie (remember the secure attribute?) therefore it is redirected back to Azure AD for log in, and we keep repeating the same sequence over and over again leading to the infinite loop.

Hover over image to enlarge

Doing some research shows that this problem was reported widely by multiple people in the following forum:

https://stackoverflow.com/questions/31588552/owin-and-azure-ad-https-to-http-redirect-loop

https://github.com/aspnet/Security/issues/219

What’s the solution?

The ideal way to resolve this issue is to force https navigation to the site. It’s always a good idea to use https scheme when browsing to web sites that require authentication. There are multiple ways to force https navigation mentioned in the above links. If your scenario requires the initial navigation to happen over http, you can customize the Cookies Authentication middleware to allow the authentication AspNet cookie for both http and https scheme by setting the CookieSecure attribute to CookieSecureOption.Never as followed in the Startup.Auth.cs file:

public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                CookieSecure = CookieSecureOption.Never
            });

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
…
}

Retrieving user information from Azure AD’s UserInfo Endpoints

In OpenId Connect (OIDC) we have the UserInfo endpoint, that’s specifically for the OIDC protocol and we cannot use with OAuth2 protocol.

To use this endpoint in Azure AD we need a token, and without specifying the “Resource” parameter.

How to obtain a token (V1)

For the sake of this example we’ll use the auth code grant flow to request tokens, using Microsoft Identity Platform V1 endpoint.

In a browser we can request a token like the samples below.

  1. Request Code – without specifying the “Resource” parameter, like:
    https://login.microsoftonline.com/{tenant}/oauth2/authorize?client_id={appid}&scope=openid%20offline_access%20profile&redirect_uri={redirect_uri}&response_type=code

    Response:
    https://localhost:44555/?code=AQABAAIAAAAP0wLlqdLVToOpA4kwzSnx6BjceMnEMcvAOdHgnHs3FJWDCqJbpVc5kQWtNUD8X-bUSCT1iiYd_vmY-G6nA2RPJK7C1GnSclx3UmUvIQWrANdZfoEZGY_sfTOvZ5Y6S3FVZExIue-vj89JAcJ40nMRBN…
  2. Request Token – using Code returned request Access Token again, without specifying the “resource” parameter, request to token endpoint should look like:
This image has an empty alt attribute

Response has an Access Token, that cannot be decoded:

Call the endpoint

Do the request to UserInfo endpoint (https://login.microsoftonline.com/common/openid/userinfo) using the access token, like you can see below:

Response:

Call UserInfo with Microsoft Identity Platform V2 endpoint

Above we described how to call the endpoint using the V1 endpoint, the same can be achieved using the V2 endpoints also.

When using this authentication endpoints we’ll need to get a token with the MS Graph scope and call the UserInfo endpoint – https://graph.microsoft.com/oidc/userinfo.

How to get UserInfo details from the JWT Token

If you are requesting a id_token to begin with, you can just decode it and save yourself an additional call in your application.

Below we can see a decoded id_token and information that it has.

Existing endpoints

The information of the UserInfo endpoint that you should use depends on the authentication endpoints version. And the best way to get that info is to call the metadata endpoints.

  Metadata UserInfo
V1 https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration https://login.microsoftonline.com/common/openid/userinfo (Azure AD UserInfo)
V2 https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration https://graph.microsoft.com/oidc/userinfo
(MS Graph UserInfo)

Reference

Microsoft identity platform UserInfo endpoint

Receiving error IDX21323 or DX10311 RequireNonce…

Depending on what version of OWIN is used, you may see the following error in an MVC application using OpenID Connect middleware

IDX21323: RequireNonce is ‘[PII is hidden by default. Set the ‘ShowPII’ flag in IdentityModelEventSource.cs to true to reveal it.]’. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don’t need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to ‘false’. Note if a ‘nonce’ is found it will be evaluated.

Or

Error: “IDX10311: RequireNonce is ‘true’ (default) but validationContext.Nonce is null. A nonce cannot be validated. If you don’t need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to ‘false’.”

Asp.net OpenID Connect (OIDC) middleware uses the nonce cookie to prevent security replay attack. As the error said, the application throws the above exception when it does not see the nonce cookie in the authenticated request. Cookies are domain-based so once they are set for a particular domain all subsequent requests to that domain will contain these cookies as long as they are still valid (haven’t expired or deleted yet).

Before diving into the detail it’s important to understand how these cookies are set and used in a working flow from the following Fiddler trace:

In frame 116 the browser sends a request to the OIDC application protected by Azure AD. The application, upon checking sees that this request is not authenticated so it redirects the request to Azure AD (login.microsoftonline.com) for login. Note that the application also sets the red-circled OpenIdConnect.nonce cookie in the 302 redirect response.

hover over image to enlarge

After successful authentication (frame 120 – 228), Azure AD redirects the request back to the web application (frame 229) with the authenticated id token. The nonce cookie previously set for this domain is also included in the POST request. The OIDC middleware validates the authenticated token and the nonce cookie before it continues loading the page (via another redirect). Note that at this point the purpose of the nonce cookie is complete so it’s invalidated by the application setting the expiration attribute to expire (highlighted).

hover over image to enlarge

How does the above error occur?

There might be multiple reason. Below are a few different scenarios where this error happens.

Multiple Domains are used for the same web site:

The browser originally navigates to the web application on domain A (frame 9 below). The nonce cookie is then set for this domain and then later Azure AD sends the authenticated token to domain B (frame 91). The redirection to domain B doesn’t have any nonce cookie so the web application throws the above error.

hover over image to enlarge

The solution here is to redirect the request back to the same domain used originally after authentication. To control where Azure AD sent the authenticated request back to the application, set the OpenIdConnectAuthentications.RedirectUri property in the ConfigureAuth method below.

Note: the reply URL will also need to be configured in Azure Active Directory’s App Registration, otherwise you may run into this error.

[gist id=”f52d201787e249cc3eb82f34f53c4e00″ file=”Startup.Auth.cs”]

The application is opened from an Office Document

The scenario usually involves the user clicking on a link that opens an Azure AD protected OIDC application in Excel, Word, or any other Office document and they see the error after providing credential. The problem here is that there are multiple processes (Office and browser) involved and they don’t share cookies with each other. In the screen shot below the Word process originally navigates to the web site (frame 24 to frame 35). It then transitions the navigation over to the browser process (starting in frame 36). The nonce cookie is set originally in frame 32 in the word process when the request is redirected to Azure AD for authentication. In frame 60, the request is redirected back to the application in the browser process with an authenticated token and this is where the error occurs since the browser process does not have any nonce cookie.

hover over image to enlarge

This is a known issue due to the way Office handles http binding and how cookies work and has been discussed extensively in the following links.  There are also a few proposed workarounds in these links:

https://github.com/aspnet/AspNetKatana/issues/78

https://github.com/aspnet/Security/issues/1252

https://support.microsoft.com/en-us/help/899927/you-are-redirected-to-a-logon-page-or-an-error-page-or-you-are-prompte

https://stackoverflow.com/questions/2653626/why-are-cookies-unrecognized-when-a-link-is-clicked-from-an-external-source-i-e

https://support.microsoft.com/en-us/help/838028/how-documents-are-opened-from-a-web-site-in-office-2003

Below are some ideas to work around this issue:

  1. Set the following registry key (on 64 bit OS) on the client machine where browsing is performed to disable hlink binding (see https://social.technet.microsoft.com/Forums/en-US/fe8f827f-a801-4fc0-b820-8dc30f816467/unable-to-hyperlink-to-company-applications-office-2016-proplus for more detail)

    [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Office\9.0\Common\Internet]
    “ForceShellExecute”=dword:00000001

  2. Disable nonce cookie checking (not recommended since this has security impact)

    app.UseOpenIdConnectAuthentication(

        new OpenIdConnectAuthenticationOptions

        {

            ClientId = clientId,

            Authority = authority,

            PostLogoutRedirectUri = postLogoutRedirectUri,

            ProtocolValidator = new Microsoft.IdentityModel.Protocols.OpenIdConnectProtocolValidator()

            {

                RequireNonce = false

            }

        });

  3. Create a landing page without authentication and then use the landing page to redirect to the real site.  Use the landing page to open from Office documents.
  4. Perform browser detection in the web application to check for request from Office and return an empty html page as referenced in https://gist.github.com/bachoang/942a8223a689ceba77987b9c3f54ab61

    [gist id=”942a8223a689ceba77987b9c3f54ab61″ file=”OfficeDetection.cs”]

The OpenID Connect Nonce cookie does not have these attributes set:  SameSite=None and secure

Due to the recent SameSite cookie security update changes as documented in How to handle SameSite cookie changes in Chrome browser – Microsoft identity platform | Microsoft Docs and in Work with SameSite cookies and the Open Web Interface for .NET (OWIN) | Microsoft Docs, cookies involved in the authentication process, including the Nonce cookies, should have both of these attributes set:  SameSite=None and secure.  Cookies not having both of these attributes may get dropped by the browser, which will result in the above error.  Here is what a Fiddler trace of a working scenario should look like for the request before redirecting to Azure AD for login showing how the Nonce cookie is set:

 

To make sure both of the above requirements are satisfied you should

  1. use https protocol to navigate to the web application and
  2. For .Net application, update the .Net framework to version 4.7.2 or greater and these nuget packages – Microsoft.Owin.Security.OpenIdConnect and Microsoft.Owin to version 4.1.0 or greater.  For .Net Core applications, update the .Net Core framework to version 2.1 or greater for .Net Core v2 apps and .Net Core 3.1 for .Net Core v3 apps.

With the above configuration, here is my working code for Startup.Auth.cs

using System.Configuration;
using Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Threading.Tasks;
using Microsoft.Owin.Security.Notifications;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace NetWebAppOIDC2
{
    public partial class Startup
    {
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
        private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
        private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
        private static string authority = aadInstance + tenantId;

        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = authority,
                    PostLogoutRedirectUri = postLogoutRedirectUri,
                    RedirectUri = "https://localhost:44313",
                    
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthenticationFailed = OnAuthenticationFailed
                    }

                    // Don't use SystemwebCookieManager class here to override the default CookieManager as that seems to negate the SameSite cookie attribute being set
                    // CookieManager = new SystemWebCookieManager()

                });
        }

        private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
        {
            context.HandleResponse();
            context.Response.Redirect("/?errormessage=" + context.Exception.Message);
            return Task.FromResult(0);
        }
    }
}

AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application

Last Updated: August 23 2019

Let’s get started…

When your developing or integrating an application with Azure AD, you might see the following similar error…


AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application: ‘XXX’.


This is because the redirect_uri (when using OpenID Connect) or AssertionConsumerServiceUrl (when using SAML2) being passed to Azure Active Directory to sign-in, does not exist in the application registration.


For example, if using OpenID Connect, your authentication request might look something like this…

https://login.microsoftonline.com/common/oauth2/authorize?response_type=code
&client_id=99f00653-5600-45d1-aa19-57a297ad0a58&redirect_uri=https://www.contoso.com/signin-oidc

If you are signing in using a browser, you can also see this sign-in request in the browsers address bar on the error screen.

So, we are going to check if https://www.contoso.com/signin-oidc is a reply address in the application registration for 99f00653-5600-45d1-aa19-57a297ad0a58

We got the error because https://www.contoso.com/signin-oidc is not added as a reply address in the application registration.


For SAML authentication, The sign-in request might look something like this…

https://login.microsoftonline.com/common/saml2?SAMLRequest=jZHNS8NAEMXvgv9D2Hu...

The SAMLRequest is going to look like a long value of random characters. We call this a Base64 encoded string that is SAML enflated. So to ‘deflate’ this in order to read the contents of the SAMLRequest, you can use the TextWizard in Fiddler or use a tool like the one below…
https://www.samltool.com/decode.php

Once the SAMLRequest is deflated, it will look something like this…

<samlp:AuthnRequest
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
ID="id6c1c178c166d486687be4aaf5e482730"
Version="2.0" IssueInstant="2013-03-18T03:28:54.1839884Z"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
AssertionConsumerServiceURL="https://www.contoso.com">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://www.contoso.com</Issuer>
<NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
</samlp:AuthnRequest>

The Assertion Consumer Service (ACS) within the SAMLRequest resembles the reply address.

So, we are going to check if ‘https://www.contoso.com‘ is a reply address in the application registration for the application ‘https://www.contoso.com’ which is identified by the apps ‘Identifier (Entity ID)’ in Azure Active Directory.

So what’s the solution?

So, to resolve this, you guessed it, ensure the redirect URI or Assertion Consumer Service URL is added to the application registration.

To do this…

  1. Sign into the Azure portal @ https://portal.azure.com
  2. Go to Azure Active Directory.
  3. Go to Application registrations.
  4. Find your app.
  5. Go to Authentication under Manage.
  6. Review your registered Redirect Uri(s).

How to determine what reply address is being used…

If you’re not sure how to collect this information, we generally like to use a HTTPS capturing tool like Fiddler (Available for Windows, macOS, and Linux).

To learn more about how you can use Fiddler, see the following article…

https://blogs.aaddevsup.xyz/2018/09/12/capture-https-traffic-with-http-fiddler/

You can look at the details of your request to Azure AD. Use the following article as a guide…
https://docs.telerik.com/fiddler/Observe-Traffic/Tasks/ViewSessionContent

Using “groups” claim in Azure Active Directory

Lets get Started!

To enable the return of groups in a claim, there are two ways…

  1. Use the application registration manifest by enabling the groupMembershipClaims property…
    https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest
  2. or if it’s a SAML application, you can enable it though the SSO configuration.

The steps on enabling groups claim is outlined in the following article…
https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims

Once enabled, groups will now be returned in the “groups” claim within a access token or ID token using OpenID Connect.


Important Note:

Id tokens will only contain the groups claim if the openid value is included in the scope parameter.


When using, the Azure Active Directory Authentication library (ADAL) for dotnet, by default you may not get the groups claim. You may need to add the scope claim with the openid value as an ExtraQueryParameter.


Moving forward…

The following groups claim description comes from https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens

Provides object IDs that represent the subject’s group memberships. These values are unique (see Object ID) and can be safely used for managing access, such as enforcing authorization to access a resource. The groups included in the groups claim are configured on a per-application basis, through the groupMembershipClaims property of the application manifest. A value of null will exclude all groups, a value of “SecurityGroup” will include only Active Directory Security Group memberships, and a value of “All” will include both Security Groups and Office 365 Distribution Lists.


Examples…

For example, if you are using OAuth2 and getting a access token for Microsoft Graph, the groups claim will not be returned. This is because the application registration of Microsoft Graph does not have the groupMembershipClaims enabled.

Keep in mind, only the audience of the access token should be consuming the access token. In this case, the audience is Microsoft Graph. Microsoft Graph currently has no need to know what groups a user is a member of.

In another example, if the audience is Azure Storage Explorer, this first-party application registration does in fact return groups because it does have groupMembershipClaims enabled. This is because Azure Storage Explorer allows you to assign groups to access its resources so it needs to know what groups a principal is a member of.

So, if you want to get the groups claim from an access token, ensure the audience is for your application registration. Then you can have your application request a access token for another resource like Microsoft Graph. Otherwise, you can use the ID token issued by Azure AD when using OpenID Connect.

An Access Token and ID Token will look something like this with the “groups” claim…

{
“typ”:
“JWT”,
“alg”: “RS256”,
“x5t”: “{x5t-value}”,
“kid”: “{kid-value}”
}
.{
“aud”: “{audience}”,
“iss”: “https://sts.windows.net/{tenant-id}/”,

“groups”: [ “group-id-1”, “group-id-2”,
…}.{signature}


There are some limitations…

GUID’s returned only.

By default, GUID’s are returned in the “groups” claim. If your group is synchronized to Azure AD using Azure AD Connect, you can then display the group name.

For more information about configuring the groups claim…
https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-group-claims

Otherwise, if they are not synchronized, you will only get the GUID. You will need to make a separate call to Microsoft Graph to retrieve another value like its Display Name.

The Microsoft Graph call will look something like this…

https://graph.microsoft.com/beta/users/{user-id}/memberOf

If using Azure AD Graph API (not recommended)….
https://graph.windows.net/{tenant-ID}/users/{user-ID}/getMemberObjects


When using the OAuth2 implicit flow and the principal is a member of more than 5 groups…

Instead of getting a “groups” claim, you will get a “hasgroups” claim.

The following description is from https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens

“hasgroups” claim: If present, always true, denoting the user is in at least one group. Used in place of the groupsclaim for JWTs in implicit grant flows if the full groups claim would extend the URI fragment beyond the URL length limits (currently 6 or more groups). Indicates that the client should use the Graph to determine the user’s groups (https://graph.windows.net/{tenantID}/users/{userID}/getMemberObjects).

In this scenario, An Access Token and ID Token will look something like this with the “groups” claim…

{
“typ”:
“JWT”,
“alg”: “RS256”,
“x5t”: “{x5t-value}”,
“kid”: “{kid-value}”
}
.{
“aud”: “{audience}”,
“iss”: “https://sts.windows.net/{tenant-id}/”,

“hasgroups”: “true”,
…}.{signature}


For all other authentication flows…

For other flows, if the number of groups the user is in goes over a limit (150 for SAML, 200 for JWT), then an overage claim will be added to the claim sources pointing at the Graph endpoint containing the list of groups for the user. So, just like above, you will need to make a additional call to the Graph API endpoint to get the groups the user is a member of.

In this scenario, An Access Token and ID Token will look something like this with the “groups” claim…

{
“typ”:
“JWT”,
“alg”: “RS256”,
“x5t”: “{x5t-value}”,
“kid”: “{kid-value}”
}
.{
“aud”: “{audience}”,
“iss”: “https://sts.windows.net/{tenant-id}/”,

“_claim_names”: { “groups”: “src1” },

“_claim_sources”: {

“src1”: { “endpoint”: “https://graph.windows.net/{tenantID}/users/{userID}/getMemberObjects” }

},
…}.{signature}


Azure AD B2C

As for Azure AD B2C, when using user flow policies, the “groups” claim is not available. You will have to use the Graph API to get the list of groups the user is a member of.


Using Microsoft Graph to get a list of groups a user is a member of.

Even though today, the endpoints provided by the claims above when the user is a member of too many groups is using the Azure AD Graph endpoint, we actually recommend using Microsoft Graph as the Azure AD Graph is being deprecated.

You can use Microsoft Graph to get a list of groups the user is a member of by following the guidance provided in the following article…

https://docs.microsoft.com/en-us/graph/api/user-list-memberof?view=graph-rest-1.0

Essentially, the Microsoft Graph call will look something like this…

https://graph.microsoft.com/v1.0/users/john@contoso.com/memberOf

Troubleshooting network related issue when using ADAL authentication library

Problem:

The problem described in this post is often seen in enterprise environment where the customer uses ADAL.Net library to run code like the following from their corpnet machine to authenticate to Azure Active Directory with a Federated account. The network topology in a corporate environment is often complex with multiple security measure put in place (proxy, firewall, router, etc…) to protect the LAN environment from the internet. Unfortunately, these security restrictions can cause problem when a request needs to reach Azure AD internet endpoint (login.microsoftonline.com) and/or ADFS server (or other Federated Server) to complete the authentication handshake.

[code lang=”csharp”]var authenticationContext = new AuthenticationContext(AuthorityUrl);

AuthenticationResult authenticationResult = null;

// Authentication using master user credentials

var credential = new UserPasswordCredential(Username, Password);

authenticationResult = await authenticationContext.AcquireTokenAsync(ResourceUrl, ApplicationId, credential);

return authenticationResult;

[/code]The AcquireTokenAsync call on line 9 above can result in Exception such as the following:

Case 1: Error:

Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException: unknown_error: Unknown error —> System.Threading.Tasks.TaskCanceledException: A task was canceled.

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()

at Microsoft.IdentityModel.Clients.ActiveDirectory.HttpClientWrapper.<GetResponseAsync>d__30.MoveNext()


at PowerBIEmbedded_AppOwnsData.Controllers.HomeController.<EmbedReport>d__9.MoveNext() in C:\Users\xxx \App Owns Data\PowerBIEmbedded_AppOwnsData\Controllers\HomeController.cs:line 49


ErrorCode: unknown_error

StatusCode: 408

Or

Case 2: Error

System.Net.Http.HttpRequestException: An error occurred while sending the request. —> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. —> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. —> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)

at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)

..

Analysis:

In these scenario, traditional troubleshooting technique using either ADAL logging or Fiddler does not yield much info since the problem lies deep in the socket connection layer. For the first Exception a quick search for error code 408 discussed here and here tells us that the request is timed out somewhere, but which request? The latter Exception in Case 2 provides a little context in revealing that some remote host is shutting down our request connection. There is quite a bit of work going on for a Federated account happening in the AcquireToken call:

  1. A home realm discovery request is made to Azure AD to find out where to send the authentication request to
  2. A request is then made to the Federated Identity Provider endpoint (usually ADFS server) to get the meta data info
  3. The account is sent to the Federated authorization endpoint for authentication to get a SAML token
  4. A request is then sent to Azure AD token endpoint with the SAML token to exchange for an OAuth2 token

We need to know how far in the pipeline this authentication mechanism is made before encountering an Exception and what is the URL and IP Address of the endpoint causing the exception. As I mentioned above unfortunately ADAL logging is not detailed enough to shed much info. In the above Case 2 Exception this is what ADAL logging looks like:

[ADAL]-Information 2019-01-04T18:22:04.1279473Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: ADAL PCL.Desktop with assembly version ‘4.4.2.0’, file version ‘4.4.2.0’ and informational version ‘4.4.2’ is running…

[ADAL]-Information 2019-01-04T18:22:21.1473375Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: === Token Acquisition started:

    CacheType: null

    Authentication Target: User

    , Authority Host: login.microsoftonline.com

[ADAL]-Verbose 2019-01-04T18:22:23.2660794Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: Loading from cache.

….

[ADAL]-Information 2019-01-04T18:22:23.3500878Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: Checking MSAL cache for user token cache

[ADAL]-Information 2019-01-04T18:22:23.3640906Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: Sending request to userrealm endpoint.

[ADAL]-Error 2019-01-04T18:22:25.5532973Z: fca00bbf-d7f2-4ec3-bb76-c5f828aa1854 – AdalLoggerBase.cs: Exception type: System.Net.Http.HttpRequestException

—> Inner Exception Details

Exception type: System.Net.WebException

—> Inner Exception Details

Exception type: System.IO.IOException

—> Inner Exception Details

Exception type: System.Net.Sockets.SocketException

at System.Net.Sockets.Socket.BeginReceive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags, AsyncCallback callback, Object state)

All we can tell from the above is that that the Exception happens after we send a home realm discovery request. Not too much to go on from here….

A corresponding for Fiddler trace for Case 2 looks like this:

The Fiddler trace tells us that the home realm discovery request (frame 20) is successful. The next expected request should be made to the ADFS server metadata endpoint (I know this from the tunnel request in frame 21 and 22) and that is missing in this trace. At this point we have an inkling that the ADFS server might have aborted our SSL connection (based on the error message) …

System.Net tracing and Network capture to the rescue….

Instruction to capture system.net tracing and Network capture are below at the end of this post. Both System.Net log and Network capture can provide extremely valuable information about the low-level TCP IP socket connection between the 2 end points. They tell us info about how the socket connection is initialized and established between the 2 IP addresses and if there is any problem with the connection.

Below is snippet of the System.Net log for Case 1:

System.Net Verbose: 0 : [17420] Entering HttpWebRequest#48342929::HttpWebRequest(uri: ‘https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/<tenant id redacted>/oauth2/authorize’, connectionGroupName: ‘1954745’)

ProcessId=17212

DateTime=2018-08-28T21:36:56.4706363Z

System.Net Verbose: 0 : [17420] Exiting HttpWebRequest#48342929::HttpWebRequest()

ProcessId=17212

DateTime=2018-08-28T21:36:56.4716363Z

System.Net Verbose: 0 : [17420] Entering ServicePoint#51812814::ServicePoint(login.microsoftonline.com:443)


System.Net.Sockets Verbose: 0 : [9244] Entering Socket#59411631::InternalEndConnect(ConnectOverlappedAsyncResult#21067700)

ProcessId=17212

DateTime=2018-08-28T21:37:17.5366363Z

System.Net.Sockets Error: 0 : [9244] Socket#59411631::UpdateStatusAfterSocketError() – TimedOut

ProcessId=17212

DateTime=2018-08-28T21:37:17.5376363Z

System.Net.Sockets Error: 0 : [9244] Exception in Socket#59411631::InternalEndConnect – A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond <IP Address redacted>.

ProcessId=17212

DateTime=2018-08-28T21:37:17.5386363Z

..

System.Net Error: 0 : [15916] Exception in HttpWebRequest#48342929:: – The request was aborted: The request was canceled..

ProcessId=17212

DateTime=2018-08-28T21:37:26.5476363Z

System.Net Verbose: 0 : [15916] Entering HttpWebRequest#48342929::EndGetResponse()

ProcessId=17212

DateTime=2018-08-28T21:37:26.5516363Z

System.Net Error: 0 : [15916] Exception in HttpWebRequest#48342929::EndGetResponse – The request was aborted: The request was canceled..

ProcessId=17212

DateTime=2018-08-28T21:37:26.5526363Z

System.Net Information: 0 : [15916] ServicePoint#51812814::CloseConnectionGroupInternal(1954745)

ProcessId=17212

DateTime=2018-08-28T21:37:26.5566363Z

We can tell from the above log that the request fails very early in the home realm discovery stage. The client sends a request to Azure AD (login.microsoftonline.com) but that request is timed out We tried running the same code in an Azure VM outside of the customer’s environment and that worked fine. This is enough evidence to conclude that the Azure AD endpoint is reachable and something within the customer’s environment is blocking our request.

Below is the snippet of the System.Net for Case 2:

System.Net Verbose: 0 : [32820] Entering HttpWebRequest#59312528::HttpWebRequest(uri: ‘https://<ADFS URL redacted>/adfs/services/trust/mex’, connectionGroupName: ‘35903099’)

ProcessId=40964

DateTime=2019-01-04T18:22:24.7492510Z


System.Net Information: 0 : [41764] Connection#1680021 – Created connection from 10.100.##.##:65057 to 10.100.##.##:443.

ProcessId=40964

DateTime=2019-01-04T18:22:25.1833208Z


System.Net Information: 0 : [41764] HttpWebRequest#59312528 – Request: GET /adfs/services/trust/mex HTTP/1.1

ProcessId=40964

DateTime=2019-01-04T18:22:25.2003015Z

System.Net Information: 0 : [41764] ConnectStream#62301924 – Sending headers


System.Net.Sockets Error: 0 : [36156] Exception in Socket#28560362::BeginReceive – An existing connection was forcibly closed by the remote host.

ProcessId=40964

DateTime=2019-01-04T18:22:25.2898109Z

System.Net.Sockets Verbose: 0 : [36156] Exiting Socket#28560362::BeginReceive()

ProcessId=40964

DateTime=2019-01-04T18:22:25.3063138Z

System.Net.Sockets Verbose: 0 : [36156] Entering Socket#28560362::Dispose()

ProcessId=40964

DateTime=2019-01-04T18:22:25.3198160Z

System.Net Error: 0 : [36156] Exception in HttpWebRequest#59312528:: – The underlying connection was closed: An unexpected error occurred on a send..

ProcessId=40964

DateTime=2019-01-04T18:22:25.3558191Z.

The above log tells us that we are failing to connect to the ADFS Server metadata endpoint. The server shuts down our request. It also tells us the IP address and port number of both the local machine and the remote ADFS server and we can use this info as a filter when looking at the corresponding network trace below in

Message Analyzer:

Filter used: IPv4.Address == xxx and ipv4.Address == yyy and TCP.Port == 443 and tcp.port == 65057

Or in Wireshark:

From the network trace, after the client completes TCP IP 3-way handshake with the ADFS server it sends a Client Hello message to start an SSL connection to the ADFS server. That SSL handshake fails when the server sends a reset command (RST) to close the connection. If the SSL connection is successful, we should see the server sending back a Server Hello message. In this case it’s the customer’s Firewall that blocks the connection.

How to capture a System.Net log:

https://blogs.msdn.microsoft.com/jpsanders/2009/03/24/my-favorite-system-net-trace-configuration-file-dumps-process-id-and-date-time-information/

How to capture a network log:

There are multiple ways to do this. I use netsh command to do this:

(run the following from an Admin command prompt):

  1. netsh trace start capture=yes persistent=yes traceFile=C:\temp\trace\trace001.etl // start tracing
  2. Repro the issue
  3. netsh trace stop  // stop tracing

Also see the following blog for more info:

https://blogs.msdn.microsoft.com/benjaminperkins/2018/03/09/capture-a-netsh-network-trace/

https://blogs.technet.microsoft.com/askpfeplat/2014/08/18/introduction-to-network-trace-analysis-using-microsoft-message-analyzer-part-1/

Summary:

I hope the above information is useful to help you diagnose Exceptions like this when using ADAL or any other Authentication library to Azure AD.