Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error Acquiring Token - MsalServiceException: AADB2C90146 #550

Closed
stumpykilo opened this issue Apr 19, 2018 · 21 comments
Closed

Error Acquiring Token - MsalServiceException: AADB2C90146 #550

stumpykilo opened this issue Apr 19, 2018 · 21 comments

Comments

@stumpykilo
Copy link

stumpykilo commented Apr 19, 2018

I have recently setup a Azure AD BC2 with two web apis and one native application. I can successfully use the Microsoft.Identity.Client nuget package in a legacy Windows Forms app and get authorization to call one of my apis. When I configure the second api with its appropriate .read and .write scopes I get this error (I've replaced my actual tenant domain with myb2c):

Users:
Error Acquiring Token:
Microsoft.Identity.Client.MsalServiceException: AADB2C90146:
The scope 'https://myb2c.onmicrosoft.com/customerapi/customer.read
https://myb2c.onmicrosoft.com/customerapi/customer.write
https://myb2c.onmicrosoft.com/vendorapi/vendor.read
https://myb2c.onmicrosoft.com/vendorapi/vendor.write offline_access openid profile'
provided in request specifies more than one resource for an access token, which is not supported.
Correlation ID: 8e9e9653-61bd-4a6f-9e42-3abeed814482
Timestamp: 2018-04-18 21:02:08Z
at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.VerifyAuthorizationResult()

Any ideas on what I'm doing wrong or what I need to do do instead?

@jmprieur
Copy link
Contributor

jmprieur commented Apr 23, 2018

the Azure AD V2 endpoint allow you to get a token for only one resource at once, however, you can let the user pre-consent for several resources. Please see How to get consent for several resources.

My impression is that you have two resources here:

  • https://myb2c.onmicrosoft.com/customerapi (with 2 scopes customer.read and customer.write)
  • https://myb2c.onmicrosoft.com/vendorapi (with 2 scopes vendor.read and vendor.write)

therefore you should use an override of AcquireTokenAsync which has the extraScopesToConsent parameter.

For instance:

string[] scopesForCustomerApi = new string[]
{
  "https://myb2c.onmicrosoft.com/customerapi/customer.read",
  "https://myb2c.onmicrosoft.com/customerapi/customer.write"
};
string[] scopesForVendorApi = new string[] 
{
 "https://myb2c.onmicrosoft.com/vendorapi/vendor.read", 
 "https://myb2c.onmicrosoft.com/vendorapi/vendor.write" 
};

var result = await app.AcquireTokenAsync(scopesForCustomerApi, 
                                                                    app.Users.FirstOrDefault(), 
                                                                    uiBehavior, 
                                                                    string.Empty, 
                                                                     scopesForVendorApi); 

This will get you an access token for the first Web API.
Then when you need to call the second one, you can call AcquireTokenSilentAsync(scopesForVendorApi);

@jmprieur
Copy link
Contributor

jmprieur commented Apr 23, 2018

@stumpykilo closing this as I believe I've answered. Feel free to reopen if you still experience the issue after doing what I recommended.

@pasn
Copy link

pasn commented May 11, 2018

@jmprieur, please re-open. We have exactly the same issue with B2C as OP. What you wrote is right for Azure AD v2. Azure AD v2's authorize endpoint accepts scopes for multiple resources followed by token endpoint calls to get each access token separately. In case of B2C, the same flow will end up with AADB2C90146 error on authorize endpoint (exactly the same exception as OP wrote about).

What we found is working is to call AcquireTokenAsync() few times to get each access token separately. When calling it for second time we can set UIBehaviour to Consent or Never to display browser only for a moment without the need to re-enter credentials. How to do it in a better way, so that we can get both access tokens without additional flashes?

PS: I know that UIBehaviour.Never would cause to hide the browser on Windows, but we need it to work on mobile platforms as well.

@jennyf19
Copy link
Collaborator

Thanks for the additional information @pasn
We'll get back to you and @stumpykilo

@jennyf19 jennyf19 reopened this May 11, 2018
@AlexSchuetz
Copy link

This problem still persists with MSAL v2.3.0.

AuthenticationResult authResult = await App.AuthenticationClient.AcquireTokenAsync(
                            AuthenticationConstants.CustomerScopes,
                            firstAccount,
                            UIBehavior.SelectAccount,
                            string.Empty,
                            AuthenticationConstants.VendorScopes,
                            AuthenticationConstants.Authority,
                            App.UiParent
                            );

Still causes the AADBC90146 error.

By the way: The authority parameter may not be empty and is not documented.

@jmprieur
Copy link
Contributor

@AlexSchuetz I'd like to understand better what the problem is.
Do you have some code to share with us?

Also I don't understand what is not documented? We have many parts of the doc where we document the authority:

Can you please elaborate what you would like to see where?
Do we need to have a B2C specific wiki page for MSAL.NET which seems like a good idea

I'd like to help, but need a bit more information.

@AlexSchuetz
Copy link

@jmprieur Unfortunatly I may not share the whole project:

Initializing AuthenticationClient:

AuthenticationClient = new PublicClientApplication(AuthenticationConstants.ApplicationID,  AuthenticationConstants.Authority)
{
    RedirectUri = AuthenticationConstants.RedirectUrl
};

The following request works as expected: AuthenticationPage is shown and after selecting the user I receive an AuthenticationResult with an AccessToken:

AuthenticationResult authResult = await App.AuthenticationClient.AcquireTokenAsync(
                            AuthenticationConstants.CustomerScopes,
                            App.UiParent);

However this request causes the AADBC90146 error. (firstAccount being NULL)

AuthenticationResult authResult = await App.AuthenticationClient.AcquireTokenAsync(
                            AuthenticationConstants.CustomerScopes,
                            firstAccount,
                            UIBehavior.SelectAccount,
                            string.Empty,
                            AuthenticationConstants.VendorScopes,
                            AuthenticationConstants.Authority,
                            App.UiParent
                            );

I just noticed, that the two scopes already differ in the host in contrast to your example. I will make a test with same hostnames and post my results.

Concerning the documentation: Acquiring tokens interactively Sorry for posting this here, as I don't know how to report this exactly.

@AlexSchuetz
Copy link

I now verified, that the hostname in the scopes is not the cause of this issue. Here is the exception message:

AADB2C90146: The scope 'https://{tenant}.onmicrosoft.com/api1/read https://{tenant}.onmicrosoft.com/api2/read offline_access openid profile' provided in request specifies more than one resource for an access token, which is not supported.

@dustynl
Copy link

dustynl commented Oct 31, 2018

I'm having exactly the same issue as @pasn and @stumpykilo :( Any developments on this?

@jmprieur
Copy link
Contributor

@AlexSchuetz @dustynl
https://{tenant}.onmicrosoft.com/api1 is not the same resource (API) as https://{tenant}.onmicrosoft.com/api2 even if they are hosted on the same host. See they have a scope (read in each case)

The limitation is that you cannot ask a token with scopes mixing Web apis in one call. This is a limitation of the service (AAD), not of the library.
you have to ask a token for https://{tenant}.onmicrosoft.com/api1/read
and then you can acquire a token silently for https://{tenant}.onmicrosoft.com/api2/read as those are two different APIS.

What shoud we do to improve this document? How to get consent for several resources

@AlexSchuetz
Copy link

@jmprieur I didn't try to get a token for both apis in one call, but to ask a token for CostumerScopes (resource 1) and o get consent for VendorScopes (resource 2) just like described in the mentioned document. I don't get to that point to be able to ask for the token of resource 2 with AcquireTokenSilentAsync(AuthenticationConstants.VendorScopes);, since already the first method throws.

To improve the mentioned document there are two things:

  1. Update the signatures and parameter section to match the actual implmentation or simply link AquireTokenAsync

  2. In section "How to get consent for several resources": Update this method-call to fit to an existing signature.

var result = await app.AcquireTokenAsync(scopesForCustomerApi, 
                                         accounts.FirstOrDefault(), 
                                         uiBehavior, 
                                         string.Empty, 
                                         scopesForVendorApi);

As mentioned above, I tried it with

AuthenticationResult authResult = await App.AuthenticationClient.AcquireTokenAsync(
                            AuthenticationConstants.CustomerScopes,
                            firstAccount,
                            UIBehavior.SelectAccount,
                            string.Empty,
                            AuthenticationConstants.VendorScopes,
                            AuthenticationConstants.Authority,
                            App.UiParent
                            );
  1. Either make the above method-call not result in AADB2C90146 or prrovide another way of "How to get consent for several resources". Or just mention that it is not possible (at the moment?)

@jmprieur
Copy link
Contributor

jmprieur commented Nov 1, 2018

Thanks for clarifying, @AlexSchuetz. I had lost the context that this was B2C. I'll update the docs
@parakhj : this scenario (acquiring a token for a set of scopes, and also consenting for scopes) does not seem possible with B2C (it is possible with AAD)

@parakhj
Copy link

parakhj commented Nov 11, 2018

@jmprieur and @AlexSchuetz you need to make individual calls separately to the authorize endpoint for getting tokens to different API's. Did you try that or did you run into problems there too?

We do not support user consent (only admin consent).

@jmprieur
Copy link
Contributor

Thanks @parakhj for clarifying. So the extraScopesToConsent probably does not make sense in B2C (as it's about user consent). I've updated the wiki page.

@nkumars
Copy link

nkumars commented Jun 14, 2019

@jmprieur, is there any way to get access token from different applications without prompting the user login again in B2C?

@ak-ambi
Copy link

ak-ambi commented Jun 14, 2019

You can check up my previous comment. I proposed workaround that I was using at a time. It works fine on Windows, but there was screen flickering visible on iOS and Android. Haven't tried it on newer MSAL versions, though.

@jennyf19
Copy link
Collaborator

@ak-ambi @nkumars @pasn You can use Prompt.NoPrompt which will not send any prompt behavior to the server and the server will determine which UI to show based on the previous sessions, if any. You should not see any flickering or UI flashes.

@nkumars
Copy link

nkumars commented Jun 17, 2019

@jennyf19, your solution is not working to me. I followed exactly as you said.

Let me explain you what exactly I do in my application:

  1. I sign-in using below code
authResult = await App.PublicClientApp.AcquireTokenInteractive(App.DefaultApiScopes)
                    .WithAccount(GetUserByPolicy(accounts, App.PolicySignUpSignIn))
                    .WithPrompt(Prompt.Consent).WithAuthority(App.Authority)
                    .WithExtraQueryParameters(
                        new Dictionary<string, string>
                        {  { "ui_locales", "da" },
                        { "nativeApp", "true" } })
                    .ExecuteAsync();
var accessToken = authResult.AccessToken

NOTE: DefaultApiScopes are those I always want to authorize the user
2. I want to call an Api hosted in different domain and the code to get the access token is as below:

AuthenticationResult result = await App.PublicClientApp.AcquireTokenInteractive(App.ApiScopes)
                    .WithAccount(App.Account)
                    .WithPrompt(Prompt.NoPrompt)
                    .WithAuthority(App.Authority).ExecuteAsync();
                var accessToken = result.AccessToken;

Here is accessToken returns null.

Can you please find a fix to it.

@jennyf19
Copy link
Collaborator

jennyf19 commented Jun 17, 2019

@nkumars Which authority are you using for result? You may not be providing the right scopes, so I would check that first.

Also, you might want to try removing .WithAccount(App.Account) and .WithAuthority(App.Authority) and even .WithPrompt() to see if that makes any difference. .WithAccount is optional and provides a hint to the STS about which user to get a token for, and it's not clear based on the code above, if you are pulling that value from the previous authResult or not.

@nkumars
Copy link

nkumars commented Jun 18, 2019

@jennyf19, in both the method calls, I'm using the below Authority;

https://{tenantId}.b2clogin.com/tfp/{tenant}/{policy}/v2.0/.well-known/openid-configuration

I had even tried using a different authority (below) with no luck;

https://login.microsoftonline.com/tfp/{tenant}/{policy}/oauth2/v2.0/authorize

I then tried to follow you, removing .WithAccount(App.Account) and .WithAuthority(App.Authority), I received the below error.

AADSTS50049: Unknown or invalid instance.
Trace ID: 1996cdb5-fcca-4553-b21e-6e3d253b2100
Correlation ID: 8f034d5f-8689-4bbb-b7cc-907b5c98ee61
Timestamp: 2019-06-18 06:05:45Z

I then later tried removing .WithPrompt(), also by giving .WithPrompt(Prompt.NoPrompt). Nothing seem to be working to me.

result will either be null or the line throws an exception with message as above :(

@jennyf19
Copy link
Collaborator

@nkumars how are you creating the public client? You will need to use

string Authority = "https://{tenantId}.b2clogin.com/tfp/{tenant}/{policy}/"
.WithB2CAuthority(B2CConstants.Authority)

Which will look like this:

 PCA = PublicClientApplicationBuilder.Create(B2CConstants.ClientID)
                .WithB2CAuthority(B2CConstants.Authority)
                .WithRedirectUri($"msal{B2CConstants.ClientID}://auth")
                .Build();

and this for the Acquire Token call:

IEnumerable<IAccount> accounts = await PCA.GetAccountsAsync();
AuthenticationResult ar = await PCA.AcquireTokenInteractive(B2CConstants.Scopes)
                .WithAccount(GetAccountByPolicy(accounts, B2CConstants.PolicyEditProfile))
                .WithPrompt(Prompt.NoPrompt)
                .WithAuthority(B2CConstants.AuthorityEditProfile) // if you're using a different authority then the one used with the PCA
                .WithParentActivityOrWindow(ParentActivityOrWindow)
                .ExecuteAsync();

Another idea is to try this B2C sample? You can just switch out all the values here and use your own and see if this works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants