[SOLVED] AcquireTokenByAuthorizationCode throw new exception in single tenant application using ASP.NET MVC using Azure Active Directory

Issue

I tried this tutorial because I want to use the Microsoft Graph API to create massive teams in Microsoft Teams.
The only difference with the tutorial is that I used the next choice in Authentication section of Azure AD admin center:

"Accounts in this organizational directory only (myuniversity only - Single tenant)"

Because of this I changed my code to use endpoint for single tenant

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

    app.UseCookieAuthentication(new CookieAuthenticationOptions());

    app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            
            ClientId = appId,
            //Authority = "https://login.microsoftonline.com/common/v2.0",//
            Authority = "https://login.microsoftonline.com/{tenantid}/v2.0",
            Scope = $"openid email profile offline_access {graphScopes}",
            RedirectUri = redirectUri,
            PostLogoutRedirectUri = redirectUri,
            TokenValidationParameters = new TokenValidationParameters
            {
                // For demo purposes only, see below
                ValidateIssuer = false

                // In a real multi-tenant app, you would add logic to determine whether the
                // issuer was from an authorized tenant
                //ValidateIssuer = true,
                //IssuerValidator = (issuer, token, tvp) =>
                //{
                //  if (MyCustomTenantValidation(issuer))
                //  {
                //    return issuer;
                //  }
                //  else
                //  {
                //    throw new SecurityTokenInvalidIssuerException("Invalid issuer");
                //  }
                //}
            },
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = OnAuthenticationFailedAsync,
                AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
            }
        }
    );
}

After authentication of the user the code I run to OnAuthorizationCodeReceivedAsync method but got an exception in AcquireTokenByAuthorizationCode method

Here is the method

private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
    var idClient = ConfidentialClientApplicationBuilder.Create(appId)
        .WithRedirectUri(redirectUri)
        .WithClientSecret(appSecret)
        .Build();
    var accounts = await idClient.GetAccountsAsync();
    string message;
    string debug;

    try
    {
        string[] scopes = graphScopes.Split(' ');

        var result = await idClient.AcquireTokenByAuthorizationCode(scopes, notification.Code).ExecuteAsync();

        message = "Access token retrieved.";
        debug = result.AccessToken;
    }
    catch (MsalException ex)
    {
        message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
        debug = ex.Message;
    }

    var queryString = $"message={message}&debug={debug}";
    if (queryString.Length > 2048)
    {
        queryString = queryString.Substring(0, 2040) + "...";
    }

    notification.HandleResponse();
    notification.Response.Redirect($"/Home/Error?{queryString}");
}

The exception is:

AcquireTokenByAuthorizationCodeAsync threw an exception

AADSTS50194: Application ‘application id'(ASP.NET Graph Tutorial) is
not configured as a multi-tenant application. Usage of the /common
endpoint is not supported for such applications created after
’10/15/2018’. Use a tenant-specific endpoint or configure the
application to be multi-tenant. Trace ID:
5f0fbf2e-5d63-40d4-a833-ca8627a02d00

Correlation ID: 3ec4ec7b-0c86-4e2b-a053-9823f977499d Timestamp:
2021-02-16 20:21:03Z

I want to use single-tenant authentication for my organization only

Solution

If you want to require AD token from one specific tenant with MSAL.NET, you can tell the SDK from which tenant to obtain the token by mentioning the specific Authority. For more details, please refer to here.

For example

 private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
        private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
        private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
        private static string graphScopes = ConfigurationManager.AppSettings["ida:AppScopes"];
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = appId,
                    Authority = "https://login.microsoftonline.com/<your tenant id>/v2.0",
                    Scope = $"openid email profile offline_access {graphScopes}",
                    RedirectUri = redirectUri,
                    PostLogoutRedirectUri = redirectUri,
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        // For demo purposes only, see below
                        ValidateIssuer = false

                        // In a real multi-tenant app, you would add logic to determine whether the
                        // issuer was from an authorized tenant
                        //ValidateIssuer = true,
                        //IssuerValidator = (issuer, token, tvp) =>
                        //{
                        //  if (MyCustomTenantValidation(issuer))
                        //  {
                        //    return issuer;
                        //  }
                        //  else
                        //  {
                        //    throw new SecurityTokenInvalidIssuerException("Invalid issuer");
                        //  }
                        //}
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthenticationFailed = OnAuthenticationFailedAsync,
                        AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
                    }
                }
            );
        }

        private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
        {
            var idClient = ConfidentialClientApplicationBuilder.Create(appId)
                 .WithRedirectUri(redirectUri)
                 .WithClientSecret(appSecret)
                 .WithAuthority("https://login.microsoftonline.com/<your tenant id>")
                 .Build();

            string message;
            string debug;

            try
            {
                string[] scopes = graphScopes.Split(' ');

                var result = await idClient.AcquireTokenByAuthorizationCode(
                    scopes, notification.Code).ExecuteAsync();

                message = "Access token retrieved.";
                debug = result.AccessToken;
            }
            catch (MsalException ex)
            {
                message = "AcquireTokenByAuthorizationCodeAsync threw an exception";
                debug = ex.Message;
            }

            var queryString = $"message={message}&debug={debug}";
            if (queryString.Length > 2048)
            {
                queryString = queryString.Substring(0, 2040) + "...";
            }

            notification.HandleResponse();
            notification.Response.Redirect($"/Home/Error?{queryString}");
        }

        private Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            notification.HandleResponse();
            string redirect = $"/Home/Error?message={notification.Exception.Message}";
            if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
            {
                redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
            }
            notification.Response.Redirect(redirect);
            return Task.FromResult(0);
        }

Answered By – Jim Xu

Answer Checked By – Gilberto Lyons (BugsFixing Admin)

Leave a Reply

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