When the application is re-launched it is not persisting previously logged in user. how to make the user persist when application is re-launched?
The following exception is throwing while trying to acquire token silently when application is re-launched.
{Microsoft.Identity.Client.MsalUiRequiredException: Null user was passed in AcquiretokenSilent API. Pass in a user object or call acquireToken authenticate.
Is there anyway to handle it?
Hello @krishnahitachi
I suspect you are using ADAL.NET for a Desktop application (.NET framework)?
Is you are indeed building a .NET Desktop application, you can find an example of implementation provided in the active-directory-dotnet-desktop-msgraph-v2 sample.
Also note the patter to use: acquireTokenSilent, and it it fails call acquireToken
I have a related issue. My console app uses the registry to store the access token and expiry timestamp. So, it shows the consent screen once and then, if they use the console app again within an hour it doesn't show the consent screen because the cached access token is still valid. If the expiry date is now lapse, I get it to show the consent screen again.
I find this annoying. With the Google Calendar API once you can authorized your app for consent that it is. It continues to work. So if my users decide to use my Outlook console app instead to sync with their Outlook calendar it is going to be annoying that it will keep asking for consent every hour.
Your documentation indicates this is handled by the RefreskToken but this is not exposed. One of your staff said it is handled internally but I can't see how that is the case, atleast, not with how I am doing things.
Once my user has consented I would like it to stay that way for them else it is not a nice user experience. Please help me fix this issue too because I am reluctant to add this Microsoft Graph Console app to my application features until it is resolved for me.
Thank you.
@ajtruckle : Do I understand that you stored only the access token to the registry? If this is the case, It's not enough (you are missing the refresh token for instance).
What you really need to do is provide your own implementation of a serializer to store the content of the cache (which contains the necessary stuff) to the registery if this is your choice. You can do that by setting the SetBeforeAccess, SetAfterAccess, and SetBeforeWrite extension methods of the TokenCachen class in the same way it's done for a file in https://github.com/Azure-Samples/active-directory-b2c-dotnet-desktop/blob/master/active-directory-b2c-wpf/TokenCacheHelper.cs.
BTW, when you write 'consent screen', do you mean the sign-in screen? Or do your user need to re-consent to authorize your application to access the data in his name every hours?
BTW, when you write 'consent screen', do you mean the sign-in screen? Or do your user need to re-consent to authorize your application to access the data in his name every hours?
The latter "user need to re-consent to authorize your application to access the data".
I do only store the token in the registry and expiry date. I am happy to use an external file. I didn't understand the examples about the TokenCache in the examples I looked at. Can help me please? This sounds like my missing step.
@ajtruckle: for your first point, would you know if the users seeing the consent screen every hour are using Work/School accounts or MSA (that is Microsoft Personal accounts, often Hotmail, live, ... but the email address itself can be anything)? The reason I'm asking is we observed since Friday that this was happening with MSAs, and we've raised a bug #468 about it which we are investigating.
As far as helping you to implement the token cache, to store the content of the token cache, you need to;
PublicClientApplication your as shown here (passing the cache that you get by calling TokenCacheHelper.GetUserCache(): https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2/blob/master/active-directory-wpf-msgraph-v2/App.xaml.cs#L19: clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
I hope this helps.
I will look, thanks. It will not be work or school accounts. Just user accounts as far as I am aware. To create events in their own calendar that they share with others themselves.
Sent from my iPhone
On 10 Sep 2017, at 18:07, Jean-Marc Prieur <[email protected]notifications@github.com> wrote:
@ajtrucklehttps://github.com/ajtruckle: for your first point, would you know if the users seeing the consent screen every hour are using Work/School accounts or MSA (that is Microsoft Personal accounts, often Hotmail, live, ... but the email address itself can be anything)? The reason I'm asking is we observed since Friday that this was happening with MSAs, and we've raised a bug #468https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/468 about it which we are investigating.
As far as helping you to implement the token cache, to store the content of the token cache, you need to;
If you really want to save the content of the cache to the registry, change the implementation of:
AfterAccessNotification to write to the registry instead of a file this linehttps://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2/blob/master/active-directory-wpf-msgraph-v2/TokenCacheHelper.cs#L79
Construct the PublicClientApplication your as shown here (passing the cache that you get by calling TokenCacheHelper.GetUserCache(): https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2/blob/master/active-directory-wpf-msgraph-v2/App.xaml.cs#L19:
clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
I hope this helps.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/467#issuecomment-328356602, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AHnYs2egUy5jelExa_tjzXjgtZdPHLldks5shBcogaJpZM4PRGAr.
Thanks for confirming that you see #468, @ajtruckle
@jmprieur I am making progress, thanks. I just need to make some changes here as now I have two systems fighting each other.
I added the TokenCacheHelper class to my project and in my constructor I temporarily set the path:
public Outlook()
{
TokenCacheHelper.CacheFilePath = @"d:\TestCache.dat";
}
My other method that returns the graph client now needs simplifying. I can remove all the registry stuff as I now have the token cache data file. This is what I have right now:
public async Task<bool> InitAuthenticatedClientAsync()
{
string appID = "~~~";
PublicClientApplication PublicClientApp = new PublicClientApplication(appID, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
//PublicClientApplication PublicClientApp = new PublicClientApplication(appID);
string[] _scopes = new string[] { "user.read", "calendars.readwrite" };
_graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
_AuthResult = null;
string keyName = "";
if(string.Equals(Program.Options.CallingApplication, "msa", StringComparison.OrdinalIgnoreCase))
keyName = @"Software\MeetSchedAssist\Meeting Schedule Assistant\OutlookCalendar\Security";
else if (string.Equals(Program.Options.CallingApplication, "pts", StringComparison.OrdinalIgnoreCase))
keyName = @"Software\Community Talks\Public talks\OutlookCalendar\Security";
string token = "";
string expire = "";
bool bPromptUser = false;
RegistryKey regKey = Registry.CurrentUser.OpenSubKey(keyName, false);
if (regKey == null)
bPromptUser = true;
else
{
token = (string)regKey.GetValue("Status");
expire = (string)regKey.GetValue("Expire");
DateTime dtExpire = DateTime.Parse(expire);
if (dtExpire < DateTime.Now)
{
// There is no point using this token as it has expired
// So we need to show the prompt
bPromptUser = true;
}
}
if(bPromptUser)
{
_AuthResult = await PublicClientApp.AcquireTokenAsync(_scopes); //Opens Microsoft Login Screen
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyName))
{
key.OpenSubKey(keyName, true);
key.SetValue("Status", _AuthResult.AccessToken);
key.SetValue("Expire", _AuthResult.ExpiresOn.ToLocalTime().ToString());
key.Close();
token = _AuthResult.AccessToken;
}
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
else
{
token = (string)regKey.GetValue("Status");
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
}));
try
{
Microsoft.Graph.User me = await _graphClient.Me.Request().GetAsync();
}
catch (Exception ex)
{
SimpleLog.Log(ex);
Console.WriteLine("GetAuthenticatedClientAsync: See error log.");
return false;
}
return true;
}
So at the moment I am getting two consent screens. What should the basic approach be now for getting the graph client correctly?
Think I got it from your samples!
public async Task<bool> InitAuthenticatedClientAsync()
{
string appID = "~~~";
PublicClientApplication PublicClientApp = new PublicClientApplication(appID, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
string[] _scopes = new string[] { "user.read", "calendars.readwrite" };
try
{
_AuthResult = await PublicClientApp.AcquireTokenSilentAsync(_scopes, PublicClientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
_AuthResult = await PublicClientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException msalex)
{
SimpleLog.Log(msalex);
Console.WriteLine("GetAuthenticatedClientAsync: Acquire token error. See log.");
}
}
catch (Exception ex)
{
SimpleLog.Log(ex);
Console.WriteLine("GetAuthenticatedClientAsync: Acquire token silently error. See log.");
return false;
}
return true;
}
Yes, this is better already, but you really want to do the try/catch in the implementation of the DelegateAuthenticationProvider.
@jmprieur Got it!!!! Here we are:
public bool InitAuthenticatedClientAsync()
{
string appID = "~~~";
PublicClientApplication PublicClientApp = new PublicClientApplication(appID, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
string[] _scopes = new string[] { "user.read", "calendars.readwrite" };
bool bSuccess = true;
_graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
try
{
_AuthResult = await PublicClientApp.AcquireTokenSilentAsync(_scopes, PublicClientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
_AuthResult = await PublicClientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException msalex)
{
SimpleLog.Log(msalex);
Console.WriteLine("GetAuthenticatedClientAsync: Acquire token error. See log.");
bSuccess = false;
}
}
catch (Exception ex)
{
SimpleLog.Log(ex);
Console.WriteLine("GetAuthenticatedClientAsync: Acquire token silently error. See log.");
bSuccess = false;
}
if(bSuccess)
{
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", _AuthResult.AccessToken);
// Get event times in the current time zone.
requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
}
}));
return bSuccess;
}
@ajtruckle : great to read that you managed it. Is your initial problem solved now?
@jmprieur You asked
Is your initial problem solved now?
Yes, I have done another test this morning and I am no longer getting the popup credentials window etc. Thank you. I do still have some generic questions but I will raise other issues for those.
Thanks again. :)
Great to read, @ajtruckle
Fro questions; you know that you can get an answer quicker, and help other users learn by posting your questions to https://stackoverflow.microsoft.com/questions/tagged/adal.
@krishnahitachi : did the answer solve your problem?
@jmprieur Yes, I have already asked several there. Albeit not with that adal tag. You provided a resolution that was not supplied to my question:
So I don't know if you want to replicate your answer there and I will tick accept.
Most helpful comment
@ajtruckle : Do I understand that you stored only the access token to the registry? If this is the case, It's not enough (you are missing the refresh token for instance).
What you really need to do is provide your own implementation of a serializer to store the content of the cache (which contains the necessary stuff) to the registery if this is your choice. You can do that by setting the SetBeforeAccess, SetAfterAccess, and SetBeforeWrite extension methods of the TokenCachen class in the same way it's done for a file in https://github.com/Azure-Samples/active-directory-b2c-dotnet-desktop/blob/master/active-directory-b2c-wpf/TokenCacheHelper.cs.
BTW, when you write 'consent screen', do you mean the sign-in screen? Or do your user need to re-consent to authorize your application to access the data in his name every hours?