Closed dfibuch closed 3 years ago
@dfibuch You're not getting an access_token because no access_token was requested, if you need an access_token you should request a scope for a resource you're trying to access (openid
and profile
will usually only return an idToken).
As for the interaction_in_progress
error and login loop, please make sure handleRedirectPromise
has resolved before calling loginRedirect
or one of the getAccount
APIs. If you're certain handleRedirectPromise
has finished before calling another msal function and you're still seeing this, please share some code snippets of how you're using msal and/or msal logs so we can help you debug. Thanks!
Cheers for the detailed reply @tnorling ! Can you provide me some more details about how I go about requesting the access_token
? I take it something around this has changed in this latest version because I didn't have to request it before.
As to the error, I can say for certain that my login code gets called again before the promise has resolved as I can see this happening in console. This is because I automatically call login
when the user hits the homepage, then they go to the login screen, get redirected back to the homepage of the app (which calls login again), then the promise resolves and user is logged in. So I think I need to change where I redirect the user after the initial login so that my login
code doesn't get called, then when the promise resolves take them to the home page.
@dfibuch You should get an access_token if you request a scope associated with a resource. openid
and profile
release information about the user which is usually contained in an id_token. Can you confirm that you were getting an access_token using these same scopes before and can you confirm which version that happened in? I would not expect a code change to change what the server responds with and the behavior you describe does not surprise me so I would be curious to know why you were getting the access_token before.
I was (and still am on our UAT system) performing the same request with the same scopes in msal-browser v2.1.0 and I get my access token, though it seems only on acquireTokenSilent
(I get the token in both situations with Azure AD, but this is ADB2C)
And this is my silentRequest
(same one I am making before and after update to 2.2.0):
@dfibuch In this example you are passing a resource scope which I would expect to return an access_token. The example you provided in the original post only contained openid
, profile
and offline_access
which I would expect to only return an id_token. Is the original post incorrect or do you have 2 separate requests?
Sorry, I can see the confusion. In the original post I was showing my loginRedirect
return which I was expecting to give me the access_token
but you correctly pointed out that this doesn't (at least not for ADB2C sign in), and I can confirm this is the same behaviour for 2.1.0.
In my 2nd example you can see that my acquireTokenSilent
is returning an access_token
(2.1.0) but this was not the case for 2.2.0 doing the same request. If you'd like I can swap to my 2.2.0 branch and run the test again to get screenshots.
Hope this clears it up. And thank you for your continued assistance and patience.
@dfibuch I think some network logs would be useful here. Could you capture your network traffic when using both 2.1.0 and 2.2.0 and I can take a look to see what might be different? You can send it to the email on my profile.
I am having the same issue. However, I'm also having it with 2.1.0. acquireTokenSilent
with { account: it, scopes: [ 'scopeurl'] }
is returning a response with an idToken, but no accessToken. This is happening after the callback from loginRedirect
.
Where request
is { account: accountobject, scopes: [ 'scopeurl' ] }
let token = await uaa.acquireTokenSilent(request);
console.log(token);
Outputs:
accessToken: ""
account: {homeAccountId: "adasdasdasd", environment: "asdasdasd", tenantId: "", username: "sdfsdfsdf", name: "asdasdasd"}
expiresOn: null
extExpiresOn: null
familyId: null
fromCache: false
idToken: "eyJ0eXAiO...."
scopes: []
I should note, acquireTokenPopup
actually does seem to work. Returns an object with accessToken
and scopes
populated.
@wasabii Can you do some debugging on your end and determine if acquireTokenSilent
is returning from cache or making a network call?
If it's making a network call can you determine if: A. The requested scopes are included on the request B. The access token is in the response
If you don't know how to inspect the request/response feel free to capture the network trace and email it to me. Thanks!
There is a form POST to /oauth2/v2.0/token
. The form data contains scope: https://blahblahblah.onmicrosoft.com/blah-dev1-api/Api openid profile
for grant_type
refresh_token
.
The result only has idToken
present in it.
The additional openid
and profile
scopes are probably the issue. These are being added automatically.
Sorry @tnorling , I haven't forgotten about this, just been prioritised on other things for time being but I will get back to it if you guys haven't figured it out already.
@wasabii @dfibuch I'm not able to repro this on my end. Can you double check that your app registration has the redirectUri configured as type "spa", implicit grant settings are disabled and that the scopes are exposed? Can you also share what policy you are using and the identity provider you are trying to login with? Unfortunately without being able to reproduce this and without any logs it's difficult for me to say what the issue may be.
Given that your request contains the required scopes but the response does not contain what we would expect, this may be a server problem. You can file a support ticket on the service by following these instructions
The additional openid and profile scopes are probably the issue. These are being added automatically.
openid
and profile
are added by the library by default to all requests as the server expects these scopes to be present. This is by design.
It is registered as a SPA.
Simply changing it to acquireTokenPopup
works fine, by the way. I can change it, it works. Change it back, it no longer works. One thing.
@wasabii That tells me it's related to refresh token redemption, are you passing the offline_access
scope? But again it's working as expected with my sample app so it could be an issue with either the policy or the IDP you're using. I would suggest you open a ticket with the service so they can take a look on their end. If you can provide a reproduceable sample I can also bring it up internally.
@tnorling My app-registration
API Permissions
Using acquireTokenPopup
like @wasabii suggested, with the same SilentRequest
as always and a single scope for my execute
api I get the accessToken
When switching to acquireTokenSilent
I no longer get my accessToken
and also I've noticed the scopes
that it returns is now empty and above it has the scope I originally requested
Weirdly enough, when I first used the popup method and then changed the code to use silent (without clearing any cookies/session) the silent method returned the accessToken
so its almost like whatever code runs before it doesn't set something correctly to be used in the silent method?
EDIT: Just to point out that this whole thing still works in v2.1.0 and gives me my accessToken
and scopes
when acquireTokenSilent
is called with the same silentRequest
.
Weirdly enough, when I first used the popup method and then changed the code to use silent (without clearing any cookies/session) the silent method returned the accessToken so its almost like whatever code runs before it doesn't set something correctly to be used in the silent method?
acquireTokenSilent
will first attempt to find the token in the cache, so this is likely why you got the accessToken after calling acquireTokenPopup
first. It sounds like the issue happens when exchanging the refreshToken for new tokens, which happens if there is no cached accessToken or if the cached accessToken is expired.
Is that an issue with how we've configured something our end or internals of the package?
@dfibuch It looks to me to be an issue with either the B2C policy you're using or the downstream IDP you're logging in with. I may be able to confirm with network logs and if you can confirm for me your scenario or provide a reproduction (preferably using one of our samples) that may help me investigate. I've tried running our own b2c sample against the latest version and have not been able to reproduce this behavior so it's hard to say with certainty at this point what the root cause is.
@tnorling I'll try to get some time to look at this again tomorrow (seems unfortunate that due to our timezones I finish when you start 😂). Unless @wasabii is able to provide some of this info as well.
I did find that I had those implicit settings enabled. Disabling them did fix it in 2.1. So, I'm trying to get 2.2 working to properly align with the original bug.
However now I'm struggling with a different issue where popup is actually presenting a login screen in a popup. When I figure that guy out, I'll let y'all know where it stands.
Okay. Cleared that up. So, on 2.2, I'm getting an empty accessToken. Call to acquireTokenSilent happens pretty closely after the return to the site after login. But yeah, empty accessToken in 2.2.
I'm also getting an empty accessToken after upgrading to version 2.2. I tried switching the msal.interceptor.ts line 32 to 'return this.authService.acquireTokenPopup' and this returns an accessToken. I'm currently using the Angular10-Browser-Sample code, but changed the config to use my B2C server and access my API. I changed the B2C client application registration to match the setting listed in https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#configure-platform-settings and https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#redirect-uri-setup-required-for-single-page-apps.
Let me know what I can share to help debug this?
A couple useful things that might help us out:
Thanks!
Ok I was finally able to repro this behavior on my end:
openid
, profile
, receive idToken and refreshToken in the response.acquireTokenSilent
with your resource scope, MSAL attempts to exchange refreshToken from step 1 for an accessToken with the requested scope + openid
and profile
. No accessToken returned in the response.A workaround for now would be to include your scope in the initial loginRedirect
or loginPopup
call or to call acquireTokenRedirect
or acquireTokenPopup
before calling acquireTokenSilent
. I'll work with the server team to understand why this is happening.
Ahh. I explicitly do not include my scopes there, as I need scopes from two different resources. And including both breaks it. Not supported or something.
Yes, you do need to make 2 separate requests for 2 separate resources. Are you still getting this behavior if you include the 2nd resource set in extraScopesToConsent
?
For example:
msal.loginPopup({
scopes: ["scope_resource1"],
extraScopesToConsent:["scope_resource2"]
});
msal.acquireTokenSilent({scopes: ["scope_resource1"]})
msal.acquireTokenSilent({scopes: ["scope_resource2"]})
@tnorling I sent you an email, but here are the answers to the troubleshooting tips you listed above.
@tnorling Do you have any updates from the server team, and any idea of timing for a fix? We can go forward with the suggested workaround for now during development (we've confirmed that it works), but that will not work in production (redirects tear down in-progress user work, popups don't work with Safari users). Thanks!
@tnorling I gave it a go. Not supported:
AADB2C90146:+The+scope+'https://xxx/analyticssimulator-dev1-web/Web+https://xxx/analyticssimulator-dev1-api/Api+openid+profile'+provided+in+request+specifies+more+than+one+resource+for+an+access+token,+which+is+not+supported.
Update: When exchanging a refresh token for a new access token the server will only respond with an access token for the scopes that were requested when obtaining that refresh token. This was by design but we are going to work with the server team to see if this behavior can be changed. Full disclosure: it will probably take some time to decide on and implement a fix.
The reason it appeared to work in a previous version was because acquireTokenSilent
falls back to ssoSilent
when the refresh fails and ssoSilent goes through the full flow of getting and exchanging an Auth Code for new tokens (this is what was succeeding, not the exchange of the refresh token). When we updated the refresh flow to include scopes openid
and profile
, the refresh succeeded with a refreshed id_token and silently rejected the access token scope since it was not included in the initial request.
So now that we understand the behavior, this is the workaround until we have a more concrete solution for B2C:
Call loginRedirect
or loginPopup
with your first set of scopes. acquireTokenSilent
should succeed until you need a different set of scopes. When you need a 2nd set of scopes you can call ssoSilent
in environments were 3rd party cookies are not blocked (i.e. not Safari or Chrome Incognito browsers) or acquireTokenRedirect
/acquireTokenPopup
if calling ssoSilent
is not possible. Afterwards acquireTokenSilent
should succeed for the next hour for both sets of scopes, as they will be cached. Unfortunately, once a token expires you will likely experience this problem again and you'll need to call one of the Auth Code APIs again.
I know this isn't an ideal solution but I hope it will unblock you for the time being.
Cheers for the update @tnorling , I'm glad I wasn't actually going mad when I couldn't get it to work. Is there any harm staying on v2.1.0 of the library until a fix is implemented? Are there any critical changes that I should be using in v2.2.0/2.3.0?
@dfibuch No critical updates but if you do run into any other issues upgrading to the latest version is always a great first step. Also I would like to remind everyone that B2C integration with MSAL.js version 2 is not production ready and is only recommended for development environments at this time.
Update: When exchanging a refresh token for a new access token the server will only respond with an access token for the scopes that were requested when obtaining that refresh token. This was by design but we are going to work with the server team to see if this behavior can be changed. Full disclosure: it will probably take some time to decide on and implement a fix.
The reason it appeared to work in a previous version was because
acquireTokenSilent
falls back tossoSilent
when the refresh fails and ssoSilent goes through the full flow of getting and exchanging an Auth Code for new tokens (this is what was succeeding, not the exchange of the refresh token). When we updated the refresh flow to include scopesopenid
andprofile
, the refresh succeeded with a refreshed id_token and silently rejected the access token scope since it was not included in the initial request.So now that we understand the behavior, this is the workaround until we have a more concrete solution for B2C: Call
loginRedirect
orloginPopup
with your first set of scopes.acquireTokenSilent
should succeed until you need a different set of scopes. When you need a 2nd set of scopes you can callssoSilent
in environments were 3rd party cookies are not blocked (i.e. not Safari or Chrome Incognito browsers) oracquireTokenRedirect
/acquireTokenPopup
if callingssoSilent
is not possible. AfterwardsacquireTokenSilent
should succeed for the next hour for both sets of scopes, as they will be cached. Unfortunately, once a token expires you will likely experience this problem again and you'll need to call one of the Auth Code APIs again.I know this isn't an ideal solution but I hope it will unblock you for the time being.
Hey many thx for the description of the Problem and the given workaround.
So I understand that I am responsible for now to refresh the token after it is expired at the moment?
@SBajonczak Yes, I would suggest doing something like this for now:
// Initial acquisition of scopes 1 and 2
await msal.loginPopup({scopes: ["scope1"]});
const account = msal.getAllAccounts()[0];
await msal.ssoSilent({
scopes: ["scope2"],
loginHint: account.username
});
// Subsequent token acquisition with fallback
msal.acquireTokenSilent({
scopes: ["scope1"],
account: account
}).then((response) => {
if (!response.accessToken) {
return msal.ssoSilent({
scopes: ["scope1"],
loginHint: account.username
});
} else {
return response;
}
});
Replace ssoSilent
with acquireTokenRedirect
or acquireTokenPopup
if you need to support browsers that block 3rd party cookies (i.e. Safari)
@tnorling I've just tried your workaround above and confirm it works, but you already knew that :) I've updated to v2.5.1 to get all the latest bug fixes.
Now I just need this to work with acquireTokenRedirect
.
Now I just need this to work with
acquireTokenRedirect
.
That will be awesome 😊👍
Now I just need this to work with
acquireTokenRedirect
.That will be awesome 😊👍
I will try it today and update here. My initial problem was I couldn't get acquireTokenRedirect
to work at all in my app, but I've worked it out now and got it to work, so hopefully this will be trivial.
Now I just need this to work with
acquireTokenRedirect
.That will be awesome 😊👍
So it turns out it's actually pretty easy to do. I did have to change my code slightly to either return the token from acquireTokenSilent
or nothing, then check what I got and call acquireTokenRedirect
if I didn't get a token,
const silentRequest: SilentRequest = {
account: storedAccount,
scopes: [myApiResourceScope],
};
const token = await this.msalApp.acquireTokenSilent(silentRequest)
.then(response => {
if (response.accessToken) {
return response;
}
})
// Handling for silent call failing
.catch(error => {
const authError = error as AuthError;
if (error instanceof InteractionRequiredAuthError
|| authError.errorMessage.includes("AADB2C90077")) {
// fallback to interaction when silent call fails
return this.msalApp.acquireTokenRedirect(silentRequest);
}
});
if (!token) {
return this.msalApp.acquireTokenRedirect(silentRequest);
}
return token;
Not sure if I am doing it right, but using a SilentRequest
for both acquireTokenSilent
and acquireTokenRedirect
seems to work fine.
Then in my handleRedirectPromise
I call my acquireToken
code again which first tries to do a acquireTokenSilent
(then falls back to the code above) but this time it can get one because I've already called the redirect code that ensures the token is fetched properly.
I would like to remind everyone that B2C integration with MSAL.js version 2 is not production ready and is only recommended for development environments at this time.
Hi @tnorling where is this documented? The FAQ for msal-browser specifically calls out Azure B2C as a service that it supports with no caveats. This issue (on page 3 of the issues list) and many posts down is the first mention of that I have seen.
Is there a "Support B2C in MSAL.js 2.x" issue somewhere that is tracking blockers like this one for using B2C with MSAL.js 2.0? I've been banging my head against this exact issue for over a week now, and trawling through every issue that seems tangentially related to find this important information does not seem to be the clearest way to communicate this workaround! I just don't want anyone else to get stuck in the same rabbit hole I did, or myself to get stuck in a similar rabbit hole for any other issues requiring a workaround
EDIT: Sorry! After looking at the history, your comment was posted a week before that line was added to the FAQ. My question about B2C issues still stands though, is this the only weird place that needs a workaround, or are there any other gotchas I should be aware of? Additionally, I feel like this issue should be pinned since it seems to be a big roadblock if you don't know the implementation details of the library.
@Jernik Sorry to hear this has been a source of frustration for you. This particular error and workaround are documented in the FAQ, which is also where other known issues are communicated. If you run into any issues that are not documented there please do open an issue and someone on the team will investigate and update our docs if necessary.
@tnorling ah that makes sense, in the past you had pinned issues when workarounds like this were needed, so that's where I had looked. My 2 cents on the matter of the api design is that MSAL should trigger the authorization code exchange if it doesn't get an access token back with the scopes that were requested. I don't know if that makes sense from how it's implemented, but from a consumer of the library's perspective, when I call "aquireToken", I want it to give me a token, not "maybe it gives you back a token". Either way, thanks for your work on this library! It's made interacting with Azure identity platform a lot easier and you and your team have been super responsive and helpful!
@Jernik
from a consumer of the library's perspective, when I call "aquireToken", I want it to give me a token, not "maybe it gives you back a token"
We agree the current behavior is unexpected and the B2C service team is working towards solving this in a way that will give you the behavior you describe. Unfortunately I don't have an estimate as to when that work will be completed as it is a larger undertaking than just a simple bug fix.
My 2 cents on the matter of the api design is that MSAL should trigger the authorization code exchange if it doesn't get an access token back with the scopes that were requested
MSAL doesn't make any assumptions about how you want to do the auth code exchange (popup, redirect or ssoSilent) which is why we leave this up to the application developer to handle.
MSAL doesn't make any assumptions about how you want to do the auth code exchange (popup, redirect or ssoSilent) which is why we leave this up to the application developer to handle.
That makes sense, is there any plan for an AuthorizationCodeExchangeRequired exception the same way there is an InteractionRequired exception to communicate this to consuming application? Or is checking for the access_code to be on the response the long-term way for it to be controlled?
No, the long-term plan is to "fix" the current behavior so that you don't get into a situation where the service is silently ignoring your request. Checking for the accessToken
is merely a workaround and we don't expect it to be necessary long term. You can spin up an app registration on an AAD (non-B2C) tenant to see how this should work on B2C whenever the work is completed.
@dfibuch Interactive calls such as redirect and popup should always succeed (provided you've configured everything correctly), if you do find that these are failing please open a new issue as we should understand what's going on there. acquireTokenSilent
is the only API affected by the behavior being discussed in this thread.
Ye sorry @tnorling I deleted my comment as soon as I realised my mistake. I've been off for a week and came back to "login seems to be playing up again" so started looking into it and got confused. Ignore me 🤦♂️
hi @tnorling I did use suggested workaround, but it got me into another issue. I am using msal-react in addition, not sure if it makes difference. Nevertheless if I add scope to loginPopup, the login process hangs in InProgress state
@MarekLani Can you please open a new issue and provide steps to reproduce?
@tnorling please disregard my comment, it was related to issue of re-renders invoked in react code.
Library
msal@1.x.x
or@azure/msal@1.x.x
@azure/msal-browser@2.2.0
@azure/msal-angular@0.x.x
@azure/msal-angular@1.x.x
@azure/msal-angularjs@1.x.x
Important: Please fill in your exact version number above, e.g.
msal@1.1.3
.Framework
Description
After updating to v2.2.0 from v2.1.0 and trying to login using my ADB2C account, the
accessToken
is not present for eitherloginRedirect
oracquireTokenSilent
and I get stuck in a loop of always trying to login.Using the same code, I have no issues with my ADB2C (Azure AD) app.
Side note: This error is still happening, even though it's been said it should be fixed
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.
Error Message
Security
Regression
MSAL Configuration
Reproduction steps
Call
loginRedirect
with yourAuthorizationUrlRequest
that has scopes on it, sign in with ADB2C account and in yourhandleRedirectPromise
theaccessToken
is empty.Expected behavior
Token should be returned as before
Browsers/Environment