microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

[Bug] AuthorityTenantSpecifiedTwice - AbstractApplicationBuilder.cs

Open XanthViper opened this issue 2 years ago • 9 comments

I am kind of at a loss on this issue and I am hoping someone might have a suggestion. Currently working on an application that manages its user login through Azure B2C. Application code is .NET 6, C#, Razor, and Microsoft Identity. I am using the latest packages found through Nuget. I have been able to login successfully for the past few months and as I am hoping to move this business off the ground, I decided it was time to setup an entirely new Azure subscription and with that a new B2C Tenant to work against. After going through all of the paces of setting everything up, it came time to switch over to my new B2C Tenant. I adjusted the following values as one would expect (my local secrets file) to test and make sure all is good:

"AzureAdB2C": {
    "Instance": "https://FOO.b2clogin.com/",
    "Domain": "FOO.onmicrosoft.com",
    "TenantId": "B2C-TENANT-ID-HERE",
    "ClientId": "RAZORAPPLICATION-APPLICATION(CLIENT)ID",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath ": "/signout-callback-oidc",
    "SignUpSignInPolicyId": "B2C_1_signin",
    "ClientSecret": "RAZORAPPLICATION-APPLICATION-CLIENTSECRET",
    "B2CExtensionsAppClientID": "B2CEXTENSTIONCLIENTID"
  },

When I started up my application and was presented with the B2C login screen, I logged in successfully and was redirected back to my application. However, nothing appears and I am just left with a blank browser window. Looking at my terminal window, I see some errors, and one that specifically comes up:

throw new MsalClientException(
                            MsalError.AuthorityTenantSpecifiedTwice,
                            "You specified a different tenant - once in WithAuthority() and once using WithTenant().");

This appears within this class when the exception is thrown: Microsoft.Identity.Client\AppConfig\AbstractApplicationBuilder.cs line 531 (ResolveAuthority() Method)

While the error seems obvious, for the life of me I cannot figure out where I could possibly be setting a tenant twice or if something is weirdly mixed between the AAD tenant and the B2C Tenant. I've searched for the old tenant id, instance, and domain and it just doesn't seem to exist.

After further investigation and actually able to hit a breakpoint within MSAL and was able to look at, I saw the following values for the condition that fails:

if (!aadAuthority.IsCommonOrganizationsOrConsumersTenant() && !string.Equals(aadAuthority.TenantId, Config.TenantId)) OR if (!aadAuthority.IsCommonOrganizationsOrConsumersTenant() && string.Equals(FOO.onmicrosoft.com, B2C-TENANT-ID-HERE))

According to the second bullet point here, the domain or id can be interchangable when the authority url gets created. But, this check seems to not help the situation: https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-aad-b2c-considerations#authority-for-an-azure-ad-b2c-tenant-and-policy

I have setup Auth as follows within my program.cs file:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration, Constants.AzureAdB2C)
    .EnableTokenAcquisitionToCallDownstreamApi(new string[] { builder.Configuration["API-BASESCOPEURI"] })
    .AddMicrosoftGraph(builder.Configuration.GetSection("Graph"))
    .AddDistributedTokenCaches();

builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = tokenCacheDbConnStr;
    options.SchemaName = "TOKENSCHEMANAMEHERE";
    options.TableName = "TokenCache";

    // You don't want the SQL token cache to be purged before the access token has expired. Usually
    // access tokens expire after 1 hour (but this can be changed by token lifetime policies), whereas
    // the default sliding expiration for the distributed SQL database is 20 mins. 
    // Use a value which is above 60 mins (or the lifetime of a token in case of longer lived tokens)
    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});

builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Events.OnTicketReceived = AzureB2CExtension.OnTicketReceivedCallback;
});
builder.Services.AddScoped<IClaimsTransformation, AddRolesClaimsTransformation>();

XanthViper avatar Jul 11 '22 20:07 XanthViper

@XanthViper Can you try removing `TenantId" line from the config? Here's example config too, which doesn't set TenantId.

I suspect that Microsoft.Identity.Web sets the authority on the app level to B2C and then sees that tenant is specified in the config, so it calls WithTenant using the AAD domain on the request level - not B2C, resulting in the error.

pmaytak avatar Jul 11 '22 20:07 pmaytak

@pmaytak Removing the TenantId setting from my configuration fixed the issue. Thank you!

Not sure if this is something that should be documented or adjusted on the developers' end, but this did the trick.

XanthViper avatar Jul 11 '22 23:07 XanthViper

Thanks, @XanthViper. I've reopened; I think we can improve the experience here.

pmaytak avatar Jul 12 '22 07:07 pmaytak

Issue MSAL throws AuthorityTenantSpecifiedTwice exception when domain and tenant ID are specified, as described above.

Possible causes

  • Tenant ID from the config can be passed into app options here when CCA is created.
  • There is not IsB2C check here when calling WithTenantId, unlike here. Although the exception is thrown when CCA is being created, so this WithTenantId call might be not related to the error.

Possible solutions

  1. Add IsB2C checks for above causes in TokenAcquisition class.
  2. Document somehow that Tenant Id shouldn't be specified. (Our samples already do not specify the TenantId.)
  3. Refactor authority related code in MSAL to take into account this B2C scenario. Issue: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3471

pmaytak avatar Jul 12 '22 07:07 pmaytak

@pmaytak I agree with you in that this could be improved here. I might be interpreting/reading it wrong, the documentation here (step 5) indicates that both the tenant and tenantid should be entered: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi

Maybe clarifying that documentation a bit when it comes to the Domain being used instead of the TenantId is better.

XanthViper avatar Jul 12 '22 15:07 XanthViper

Thanks @XanthViper. So it's not talking about the domain in https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi ? BTW that sample doesn't use Microsoft.Identity.Web. Does it?

jmprieur avatar Jul 14 '22 21:07 jmprieur

@jmprieur No it doesn't. And this is probably the result of me trying to get everything hooked up when I started this project. Most likely what happened is I saw that I could set the Domain and TenantId and just set them not thinking it would be an issue. Obviously I was wrong. I guess with the available settings, it just seems logical that you can set those values and not have to worry about it.

XanthViper avatar Jul 16 '22 17:07 XanthViper

@XanthViper : just to make sure. Are you on the latest version of Id.Web

jmprieur avatar Jul 19 '22 02:07 jmprieur

@jmprieur yes I am. My apologies for the late reply. Here are my package references:

<ItemGroup> <PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="6.0.2" /> <PackageReference Include="AspNetCore.HealthChecks.UI" Version="6.0.4" /> <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.4" /> <PackageReference Include="AspNetCore.HealthChecks.UI.SqlServer.Storage" Version="6.0.4" /> <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" /> <PackageReference Include="Azure.Identity" Version="1.6.0" /> <PackageReference Include="Jdenticon.AspNetCore" Version="3.1.2" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="6.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" NoWarn="NU1605" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.7" NoWarn="NU1605" /> <PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="6.0.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.24" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.7" /> <PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="6.0.7" /> <PackageReference Include="Microsoft.FeatureManagement" Version="2.5.1" /> <PackageReference Include="Microsoft.Graph" Version="4.34.0" /> <PackageReference Include="Microsoft.Identity.Web" Version="1.25.1" /> <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.25.1" /> <PackageReference Include="Microsoft.Identity.Web.UI" Version="1.25.1" /> </ItemGroup>

XanthViper avatar Jul 19 '22 23:07 XanthViper

Hi, just passing by to report having same issue within Business Central 19.5 when using Oauth2 codeunit.

I'm calling method "AcquireTokenByAuthorizationCode" and just giving the following inputs :

  • client_id
  • client_secret
  • aad authorization uri
  • callback uri
  • scopes

For reference, AL to Net bridge is here https://github.com/microsoft/ALAppExtensions/blob/0de3633075922015bf55ab5e2d355a3c58a4bdf7/Modules/System/OAuth2/OAuth2.Codeunit.al#L69

I don't know what I have to do, it was working well under 19.1 with this previous library version 😣

For instance, this was working with Business Central 19.1 which was using version 4.22.0.0 of Microsoft.Identity.Client Whereas Business Central 19.5 is using version 4.36.0.0 of Microsoft.Identity.Client

EDIT: Added the call stack trace picked from Business Central EDIT : Added libraries versions

Message (MsalClientException): RootException: MsalClientException
ExceptionStackTrace:
   at Microsoft.Identity.Client.AbstractApplicationBuilder`1.ResolveAuthority()
   at Microsoft.Identity.Client.AbstractApplicationBuilder`1.BuildConfiguration()
   at Microsoft.Dynamics.Nav.LicensingService.Client.Authentication.AuthenticationHandler.BuildClientWithTokenCache(String tenantId, String authority, ClientApplicationInfo clientInfo, Uri redirectUri, Byte[] tokenCacheState)
   at Microsoft.Dynamics.Nav.LicensingService.Client.Authentication.AuthenticationHandler.<AcquireTokenByAuthorizationCodeAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Dynamics.Nav.Runtime.AzureADCodeGrantFlow.<>c__DisplayClass12_0.<AcquireTokenAndTokenCacheByAuthorizationCode>b__0()
   at Microsoft.Dynamics.Nav.Runtime.AzureADCodeGrantFlow.GetAuthenticationInfo(Func`1 action, String operationName)
   at Microsoft.Dynamics.Nav.Runtime.AzureADCodeGrantFlow.AcquireTokenByAuthorizationCode(String tenantId, String authorizationCode, String clientId, String clientSecret, String[] scopes)
   at Microsoft.Dynamics.Nav.Runtime.ALAzureAdCodeGrantFlow.<>c__DisplayClass18_0.<ALAcquireTokensByAuthorizationCodeWithCredentials>b__0()
   at Microsoft.Dynamics.Nav.Runtime.ALAzureAdCodeGrantFlow.Catch[T](Func`1 action)
CallerStackTrace:
   at Microsoft.Dynamics.Nav.Runtime.ALAzureAdCodeGrantFlow.Catch[T](Func`1 action)
   at Microsoft.Dynamics.Nav.Runtime.ALAzureAdCodeGrantFlow.ALAcquireTokenByAuthorizationCodeWithCredentials(String authorizationCode, String clientId, String clientSecret, String[] scopes)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Dynamics.Nav.Runtime.NavDotNet.Invoke[T](String methodName, UInt32 methodIndex, BindingFlags flags, ParameterModifier modifier, Type[] referenceTypes, Object[] arguments)
   at Microsoft.Dynamics.Nav.Runtime.NavDotNet.InvokeMethod[T](Boolean isStatic, String methodName, UInt32 methodIndex, Object[] arguments)
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit502.AcquireTokenByAuthorizationCodeWithCredentials_Scope__1044184192.OnRun()
   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit502.AcquireTokenByAuthorizationCodeWithCredentials_1044184192(NavText authorizationCode, NavText clientId, NavText clientSecret, NavText redirectUrl, NavText oAuthAuthorityUrl, NavList`1 scopes, ByRef`1 accessToken)
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit502.AcquireTokenByAuthorizationCode_Scope__279554589.OnRun()
   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit502.AcquireTokenByAuthorizationCode_279554589(NavText clientId, NavText clientSecret, NavText oAuthAuthorityUrl, NavText redirectURL, NavList`1 scopes, NavOption promptInteraction, ByRef`1 accessToken, ByRef`1 authCodeErr)
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit502.OnInvoke(Int32 memberId, Object[] args)
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit501.AcquireTokenByAuthorizationCode_Scope__279554589.OnRun()
   at Microsoft.Dynamics.Nav.Runtime.NavMethodScope.Run()
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit501.AcquireTokenByAuthorizationCode_279554589(NavText clientId, NavText clientSecret, NavText oAuthAuthorityUrl, NavText redirectURL, NavList`1 scopes, NavOption promptInteraction, ByRef`1 accessToken, ByRef`1 authCodeErr)
   at Microsoft.Dynamics.Nav.BusinessApplication.Codeunit501.OnInvoke(Int32 memberId, Object[] args)

warlof avatar Jan 14 '23 07:01 warlof

Closing this as it does not repro on IdWeb 2.17.3

westin-m avatar Mar 28 '24 21:03 westin-m