-
Notifications
You must be signed in to change notification settings - Fork 214
AcquireTokenSilentAsync fails to refresh access tokens #1477
Comments
Any update on this? As mentioned in the issue, it's quite a big problem for one of our application. Any help on the matter would be greatly appreciated. |
@bgavrilMS : this is similar to some of the issues you are investigating? |
Possibly. @sourcefile - could you please update to ADAL 4.5 and repost the logs (you can email them at [email protected] as they might contain PII). We've added a few fixes in this area in ADAL 4.5 and also we have added a LOT more logging to understand how the broker is failing. |
I tried upgrading to ADAL 4.5 and running again. Unfortunately, the issue still persists. As requested, I’ve submitted the authenticator logs. The ID is BQ3AH6QZ. As a side note, I should inform you there is also a support ticket running from Azure regarding this topic. To prevent those issues from going out of sync, I'll add this ticket ID as well: 119012125000270 |
Hi @sourcefile - I would require the ADAL logs again please, not the Authenticator logs. I am not sure where you've submitted the logs - could you just post them here if they have no PII or send them via email? |
Hi @bgavrilMS I've submitted the logs via the option in the MS Authenticator itself ([Help]-->[Send Logs]). At any rate, I made a retry since I focussed on the Authenticator logs last time. The ADAL logs are below:
|
Thank you @sourcefile . First - please make sure that all the permissions are set correctly for the app, as described in our wiki: . The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them. If they are, first thing to do is to remove them and add them back. We've had cases where just "disappear". Otherwise, It looks like a similar problem that one of our teams is also facing - @arielbh had a look at this recently (thanks Ariel!) There seems to be a breaking change in Android 8.0 which is affecting this. https://developer.android.com/about/versions/oreo/android-8.0-changes#aaad which states ". The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them." I'll liaise with the ADAL for Android team to see if we can call a method on the Authenticator to get those accounts instead. |
@sourcefile - could you try to run this piece of code in your app please and tell me what is being returned? Please make sure that you have unset and re-set the permissions on the app: GET_ACCOUNTS string WORK_AND_SCHOOL_TYPE = "com.microsoft.workaccount";
var accManager = AccountManager.Get(Application.Context);
// Any accounts returned? Possibly not
Account[] accounts = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE);
// The Android docs state that "unless the authenticator owns the accounts or the user grants that access" GetAccounts will return an empty array.
// The docs state that you should call NewChooseAccountIntent so that the user grants access
Intent intent = AccountManager.NewChooseAccountIntent(null, null, new[] { WORK_AND_SCHOOL_TYPE }, null, null, null, null);
StartActivity(intent);
// Now you should have access
var accounts2 = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE); The strange thing is that my Authenticator returns the accounts everytime, and I tried on my Samsung Galaxy which is running a pretty new version of Android... |
@bgavrilMS This is what I'm getting after pasting the snippet in MainActivity.cs. First, I get a pop-up asking me to select an account. This is an android native pop-up, not the authenticator (I guess that's to be expected since we're calling Android APIs here) Both accounts here are expected. One is my work account (the redacted one), the other is the test account for the purpose of this issue. After selecting the account, This is the contents of the accounts2 variable:
As far as I can tell, the account exists, while it's accesstoken is valid. I'm currently waiting for the token to expire, but my current theory is the account is deleted during or after the attempt to refresh silently. |
@sourcefile - and before the pop-up, was the accounts variable an empty array or null? The AT will expire in 1h. |
@bgavrilMS Both variables were filled with the account. I also checked before the attempt to silently refresh and directly after but before falling back to interactive login. Every time both variables contain the account |
I keep looking and looking at the stack trace and the logs and I can't fully piece together was has happened. It looks like after the token expiration the flow was:
From what I see in the logs, the only explanation is that you haven't configured broker use in PlatformParamters. I see in your code snipped that you use a variable for this, so is it possible that you have not set it to true for this particular call ? Otherwise, I have added more logging to ADAL, and I have a nupkg on myget that you can try and might provide more clues. There are instructions here on how to install it if you need them |
Hi @bgavrilMS, Thanks for your mentioning of PlatformParameters. Although the use of broker is always set to true via a constant variable, I did not send the PlatformParams when trying to authenticate silent, resulting in the logging below.
After adjusting the code a bit to add the PlatformParameters to the silent call, I end up with the following logging (note this logging is only the refresh attempt):
If I'm reading the logger right, it seems to fail because the broker is unable to determine which account is supposed to be used. I find that odd since I'm explicitly setting the UserIdentifier when calling. Since a lot has happened since this thread began, I will post the latest version of my code as well. Please let me know if you find anything else. public class AndroidAuthenticationService : IAuthenticationService
{
private const string _tenantId = "<REDACTED>";
private const string _clientId = "<REDACTED>";
private const string _redirectUri = "msauth://<REDACTED>";
private const string _resource = "<REDACTED>";
private const bool _useBroker = true;
public AndroidAuthenticationService()
{
}
public AuthenticationResult User { get; private set; }
public async Task EnsureLoggedInAsync()
{
var context = new AuthenticationContext($"https://login.microsoftonline.com/{_tenantId}");
var tokens = context.TokenCache.ReadItems().Where(t => t.Resource == _resource);
var userId = tokens.GroupBy(t => t.DisplayableId).Count() == 1 ? new UserIdentifier(tokens.GroupBy(t => t.DisplayableId).Single().First().DisplayableId, UserIdentifierType.RequiredDisplayableId) : null;
var param = new PlatformParameters(CrossCurrentActivity.Current.Activity, _useBroker, PromptBehavior.Auto);
try
{
if (userId == null)
{
User = await context.AcquireTokenSilentAsync(_resource, _clientId);
}
else
{
User = await context.AcquireTokenSilentAsync(_resource, _clientId, userId, param);
}
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.FailedToAcquireTokenSilently || ex.ErrorCode == AdalError.InteractionRequired)
{
if (userId == null)
{
User = await context.AcquireTokenAsync(_resource, _clientId, new Uri(_redirectUri), param);
}
else
{
User = await context.AcquireTokenAsync(_resource, _clientId, new Uri(_redirectUri), param, userId);
}
}
else
{
// Unexpected kind of AdalException. Throw
throw;
}
}
} |
Ok, now it seems to not be able to get the accounts from the broker. Could you try to run the code from earlier and try again please? This is: string WORK_AND_SCHOOL_TYPE = "com.microsoft.workaccount";
var accManager = AccountManager.Get(Application.Context);
// Any accounts returned? Possibly not
Account[] accounts = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE);
// The Android docs state that "unless the authenticator owns the accounts or the user grants that access" GetAccounts will return an empty array.
// The docs state that you should call NewChooseAccountIntent so that the user grants access
Intent intent = AccountManager.NewChooseAccountIntent(null, null, new[] { WORK_AND_SCHOOL_TYPE }, null, null, null, null);
StartActivity(intent);
// Now you should have access
var accounts2 = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE); I'm also adding @trwalke who might be able to help, as I'm on holiday and traveling starting tomorrow. |
Any updates on this? PFB my code
What i cant understand is why it is removing from token cache and then again trying to open MSAuthenticator to get new token as shown in Bold below. `[Mono] Assembly Ref addref System.ServiceModel.Internals[0x729645e100] -> System[0x7296788380]: 10 ` |
@bgavrilMS @trwalke any updates on this thread? |
Hi @Anubhab1991 I am looking at your issue at the moment and I will have an update for you shortly. |
@sourcefile were you able to run the code that @bgavrilMS sent you? @Anubhab1991 can you make a successful interactive authentication call and verify if a refresh token was sent back to you in the AuthenticationResult object? can you also send the logs of this call? |
Hi @trwalke and @bgavrilMS I tried the snippet Bogdan provided, but again to no success. As far as I can see, the first time I log in, the account variables are empty. Personally, I'm not surprised as it is the first time the application starts. After logging in and waiting the usual hour, I try to refresh. I run Bogdans snippet before attempting to collect a new access token from the broker. At this stage, both account variables contain one account (the correct account), but refreshing silently is still failing. I'm still using Bogdans internal MyGet version of the ADAL package to avoid introducing too many moving parts. Please let me know if you need any more information. |
Hi @bgavrilMS While waiting for a more clean fix, we will implement this workaround in our solution, and reference the project directly. Thanks for all your help with this! |
That is quite strange. Might be a timing issue? |
I don't think so. I've triggered the pop-up several times in a single testrun and it still fails. Could it be since the code in the NuGet package is pre-compiled as opposed to including the project where it will compile at the same time as the rest of the app? |
The Android team are working hard to make the broker compatible with MSAL, our next-gen auth library. We're focusing resources on improving the experience in MSAL (e.g. bypass the account picker by using a background service), so I'm going to close this issue. |
Which Version of ADAL are you using ?
Note that to get help, you need to run the latest preview or non-preview version
For MSAL, please log issues to https://github.com/AzureAD/microsoft-authentication-library-for-dotnet
ADAL 4.4.2
Which platform has the issue?
Xamarin.Forms / Xamarin.Android
What authentication flow has the issue?
Other? - please describe;
Mobile -> Silent Authentication
Repro
When using below code in combination with the MS Authenticator Broker, the app seems to be unable to silently refresh the access token after it expires.
1.) Install broker
2.) Start application
3.) Sign in with the correct account. Everything works, data is retrieved as expected.
4.) Wait an hour for the access token to expire
5.) Start a new call to the same resource. AcquireTokenSilentAsync fails, forcing the user to go through interaction with the broker.
By providing the UserId, I can minimize the impact on the user. However, they still see the broker activities pass by and some of our applications are required to run in the background for longer periods with constant data transfer, so this workaround is not viable for me at the time.
Expected behavior
AcquireTokenSilentAsync should be able to retrieve a new access token after the previous one expires
Actual behavior
AcquireTokenSilentAsync fails, forcing an interactive refresh.
Possible Solution
Additional context/ Logs / Screenshots
I did notice in the logs caching the refresh token is skipped because of missing ClientInfo. However, I can't find no such property for me to manipulate from my code.
Logs:
The text was updated successfully, but these errors were encountered: