Microsoft Graph returning null account even after passing a valid account ID - asp.net-mvc-5

I am encountering a weird issue with Microsoft Graph on an integration that was built a few years back.
This issue started happening a few months back. After I sync a Microsoft Account and provide email and calendar read/write access, everything works fine for some time. I am able to retrieve emails and calendar events. However, after some time, I notice that when a call is made to GetAccountAsync with a valid AccountID, null is returned. This is causing AcquireTokenSilent to fail with the following error:
Error Code: user_null
Error Message: No account or login hint was passed to the AcquireTokenSilent call.
I have also noticed that this happens under the following scenarios:
When the WebJob (console app) is run every 15 minutes, I encounter this issue
To narrow down the root cause, I have deleted the WebJob to see if the issue occurs on the web app. It looks like the issue starts to occur after an hour or so even without the web job running.
I have upgraded to the latest version of MSAL and implemented 4.46.1.0 version of Microsoft.Identity.Client. I am using .NET Framework 4.8 and this is a .NET MVC 5 app.
Here's my code:
public async Task<string> GetAccessTokenAsync()
{
string accessToken;
UserExternalApp.Scope = string.IsNullOrWhiteSpace(UserExternalApp.Scope) ? "" : UserExternalApp.Scope;
// Load the app config from web.config
var microsoftScopes = UserExternalApp.Scope.Replace(' ', ',').SplitAndTrim(new char[] { ',' }).ToList();
var accountID = UserExternalApp.ExternalUserAccountID;
var app = ConfidentialClientApplicationBuilder.Create(ClientID)
.WithRedirectUri(DefaultRedirectUrl) // https:\//mywebsite.com
.WithClientSecret(Secret)
.Build();
app.AddDistributedTokenCache(services =>
{
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
options.SchemaName = "dbo";
options.TableName = "TokenCache";
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
try
{
var account = await app.GetAccountAsync(accountID);
var query = app.AcquireTokenSilent(microsoftScopes, account); // This is where the error is thrown
var acquireTokenSilent = await query.ExecuteAsync();
accessToken = acquireTokenSilent.AccessToken;
}
catch
{
// This is the error thrown:
// Exception Type: MsalUiRequiredException
// Error code: user_null
// Exception Details: No account or login hint was passed to the AcquireTokenSilent call.
throw;
}
return accessToken;
}
I know the token is persisted on my SQL Server:

I think the MSAL uses an in memory token cache by default, Once the client logins, authentication information will be stored in cookie(if cookie has not been disabled). Even your web application restarts, the client will keep logged in.
To solve this, you can use custom Token cache serialization in MSAL.NET:https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet.
Hope this helps.

I would like to share the resolution to this problem in case if it helps someone in the future. I feel that this is a Microsoft Bug that was introduced during one of their many upgrade process as this code went from working to broken without any change from our end. Here are the steps I took:
While exchanging the code for a token after user authentication, I retrieved and saved Account.HomeAccountId.Identifier, Account.HomeAccountId.ObjectId and TenantId for the account.
I implemented my own version of IAccount.
Instead of calling await app.GetAccountAsync(accountID), I used my implementation of IAccountand initialized it with the data I saved in Step 1.
I used this account to call app.AcquireTokenSilent(microsoftScopes, account).
And that's it! No error was thrown once this was done!

Related

Azure Function App: Authentication Breaks Development Portal

I've added Azure Active Directory Authentication to my function app, but as soon as I set "Action to take when request is not authenticated" to "Login with Azure Active Directory", the development interface for the function app yields this message:
Error:
We are unable to reach your function app. Your app could be having a temporary issue or may be failing to start. You can check logs or try again in a couple of minutes.
Session Id: 23a5880ec94743f5a9d3ac705515b294
Timestamp: 2016-11-16T08:36:54.242Z
Presumably adding the authentication requirement breaks access to the function app in some fashion... though I am able to make changes in the code editor, and they do take effect, I no longer see updates in the log panel: no compilation output messages, for example.
Does anyone know a work-around for this?
So far, I've tried just leaving the auth option to "Allow anonymous requests (no action)" and using this following code:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var user = "Anonymous";
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
if (claimsPrincipal != null && claimsPrincipal.Identity.IsAuthenticated)
{
user = claimsPrincipal.Identity.Name;
log.Info($"Hello {user}");
}
return req.CreateResponse(HttpStatusCode.OK, "Hello " + user);
}
However, this (rightly) doesn't redirect to the authentication provider... I would prefer to have the app take care of all that gunge for me, but if doing so means I can't see compilation messages / log messages, it makes it pretty hard to see what's going on.
Nathan,
Unfortunately, this is a limitation at the moment and we're tracking it here: https://github.com/projectkudu/AzureFunctionsPortal/issues/794
Your approach, to allow anonymous and validate in the function is what we recommend at the moment. To extend your workaround, you can add the following code to initiate a login redirect when you detect an anonymous user (the code below assumes you are using AAD).
else
{
log.Info("Received an anonymous request! Redirecting...");
var res = req.CreateResponse(HttpStatusCode.Redirect);
res.Headers.Location = new Uri(req.RequestUri, $"/.auth/login/aad?post_login_redirect_uri={req.RequestUri.AbsolutePath}&token_mode=session");
return res;
}
We understand that isn't ideal and appreciate your patience while we work to improve this.
Thanks!

Azure App Service - Some WebAPI methods throw OWIN Exception

I have an Azure Mobile App that has some methods that generate 500 errors but does not record any exceptions in Application Insights and no exceptions are thrown inside my code. I have been able to determine that normal TableController methods work fine, but custom methods do not. Also, I can remote debug the code and watch it finish executing without any exceptions being thrown. It should also be noted that I did not have this problem when this project was a Mobile Service. Here is an example method that fails:
private readonly MobileServiceContext context; //Autofac injection
private readonly IUserHelper userHelper; //Autofac injection
[HttpGet, Route("api/Site/{id}/Users")]
public async Task<HttpResponseMessage> Users(string id)
{
var userId = await userHelper.GetUserIdAsync(User, Request);
var query = context.UserSiteMaps.Include(x => x.User).Where(map => map.SiteId == id);
var auth = query.FirstOrDefault(x => x.UserId == userId && x.IsAdmin);
if (auth != null)
{
return Request.CreateResponse(HttpStatusCode.OK, query.Select(map => map.User));
}
return Request.CreateUnauthorizedResponse();
}
The deepest error log that I have been able to obtain is the detailed error page from IIS:
Module __DynamicModule_Microsoft.Owin.Host.SystemWeb.OwinHttpModule, Microsoft.Owin.Host.SystemWeb, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35_9de2321b-e781-4017-8ff3-4acd1e48b129
Notification PreExecuteRequestHandler
Handler ExtensionlessUrlHandler-Integrated-4.0
Error Code
0x00000000
I haven't been able to generate a more detailed error message and I have no idea what Owin is upset about here since other method return requests just fine. Am I doing something I shouldn't?
Any help would be greatly appreciated!
Update : Here is the full error message that I have been able to get.
I have also been able to narrow the cause down a bit. If I replace the query.Select(map => map.User) object in the response with a simple string, it returns that string without complaint. However, if I stringify the response myself and pass that in, I get 500s again. Could it be some serializer setting problem?
The best way to track down the issue is to turn on exception stack traces for you app and to turn on logging on your Mobile App backend.
See Server Side Logging in the Mobile Apps wiki and Enable diagnostics logging for web apps in Azure App Service. You can also remote debug your service to see the exact error, see Remote debugging .NET server SDK.

How to delete a video from youtube using youtube v3 api and C#

Well I am able to upload video on Youtube but i didn't find a way or relevant code to delete video/videos from Youtube.
Here is my code which i tried to delete the youtube video.
private async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.Youtube },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var videosDeleteRequest = youtubeService.Videos.Delete("Video ID");
await videosDeleteRequest.ExecuteAsync();
}
But getting 403 response
Error: Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermis
sions] Domain[global]
]
A little help or any possible solution will be highly appreciable.
The error translates to:
The video that you are trying to delete cannot be deleted. The request
might not be properly authorized.
https://developers.google.com/youtube/v3/docs/videos/delete
Have you successfully acquired the token of the user that owns the video?
The videos.delete method is preformed on private user data. In order to delete the data you must have permission or consent of the user to access their account. They must have granted you permission in one of the following scopes.
The error message
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermis
sions] Domain[global]
Means that the user did not grant you permission with a hig enough scope. If for example you asked for authorization with only a read only scope you would then not have enough permissions to delete a video.
However if we check your code we can see that you are in fact using YouTubeService.Scope.Youtube. However if you have previously run your application then the client library stored the consent of the user. If you then have changed the scope and not forced the user to consent to authorization again. Then you are still running on the old consent.
The solution in this case is to change "user" to something else which will force it to request authorization again.

Call to Action with Authorize(Roles="Customer, Business") returns 500 error for those roles

I have several actions in my MVC site that recently started returning authentication errors when I call them when logged in with accounts that have the authorized roles. An example below.
[HttpGet]
[Authorize(Roles = "Customer, Business")]
public async Task<ActionResult> ShowNotifications(bool unViewedOnly = true) {
var userId = User.Identity.GetUserId<int>();
var notifications = await _notifications.GetByUserIdAsync(userId, unViewedOnly);
return (Request.IsAjaxRequest())
? PartialView("Notifications/NotificationsModal", notifications)
: PartialView("Notifications/_Notifications", notifications);
}
I have not made any changes to the functions or the javascript that calls them in quite some time. I did recently update Microsofts Identity NuGet packages. Has anyone else seen this issue and does anyone have any idea how to fix it other than rolling back the Identity updates. I have no idea which library would have caused the problem. I am far from a security expert.
A typical response:
Key Value
X-Responded-JSON {"status":401,"headers":{"location":"http:\/\/localhost:53033\/Account\/Login?ReturnUrl=%2FShared%2FShowNotifications%3FunViewedOnly%3DTrue%26modal%3DTrue%26X-Requested-With%3DXMLHttpRequest%26_%3D1426264188044"}}

Facebook iOS SDK won't open login UI after user changes password (iOS 6)

Some context: the user had previously installed the app, authorized FB, everything worked great, then they changed their FB password (through facebook.com), deleted the app, and have now reinstalled it and are running it for the first time again after reinstall.
I am calling [FBSession openActiveSessionWithReadPermissions:allowLoginUI:completionHandler] with allowLoginUI: YES and the read permissions being "email, user_about_me, user_birthday, user_interests, user_location."
The FBSessionState I am getting in the completionHandler is FBSessionStateClosedLoginFailed. The NSLog of the error is this:
Error Domain=com.facebook.sdk Code=2 "The operation couldn’t be completed. (com.facebook.sdk error 2.)" UserInfo=0x1cd68c00 {com.facebook.sdk:ErrorLoginFailedReason=com.facebook.sdk:ErrorLoginFailedReason, com.facebook.sdk:ErrorInnerErrorKey=Error Domain=com.apple.accounts Code=7 "The Facebook server could not fulfill this access request: Error validating access token: The session has been invalidated because the user has changed the password." UserInfo=0x1cd5b970 {NSLocalizedDescription=The Facebook server could not fulfill this access request: Error validating access token: The session has been invalidated because the user has changed the password.}}
That internal error domain is ACErrorDomain and error code ACErrorPermissionDenied. So, how do I let the user re-authorize the app?
I have tried calling openActiveSessionWithReadPermissions again but that just keeps outputting the same error. I have also tried [FBSession.activeSession closeAndClearTokenInformation] but that doesn't seem to do anything (presumably because there is no activeSession).
Hitting a very similar sort of bug with 3.2.1 Facebook SDK. In my case, I get into FBSessionStateOpen but have been given an invalid access token. As the question states, the normal closeAndClearTokenInformation and even deleting the app doesn't fix it. The only way I have been able to get-back-in under this scenario is to have the user change their password in the setting app. So this is what I do.
// In my completion handler FBSessionStateOpen is called BUT an
// invalid accessToken was detected.
[session closeAndClearTokenInformation];
[FBSession renewSystemCredentials:^(ACAccountCredentialRenewResult result,
NSError *error)
{
if (result == ACAccountCredentialRenewResultFailed ||
result == ACAccountCredentialRenewResultRejected)
{
[self showErrorMessage:NSLocalizedString(#"You may need to re-enter your Facebook password in the iPhone Settings App.\n", nil)];
}
else
{
// attempt opening a session again (after they have updated their account
// settings I end up here)
[self facebookLogin]; // Performs openActiveSessionWithReadPermissions,
// but this time around the token issued should be good.
}
}];
This is the only pragmatic solution I have been able to come up with.
I think you need to get a new access token with code like this...
[FBSession.activeSession closeAndClearTokenInformation];
[[FBSession class] performSelector:#selector(renewSystemAuthorization)];
[FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
}

Resources