AcquireTokenAsync fails in a UWP app - azure-active-directory

I'm using ADAL in my Win10 UWP app. Here is a snippet of code:
// WinRT, UWP app. Doesn't work
TokenCache TC = new TokenCache();
authContext = new AuthenticationContext(authority, true, TC);
var authresult = await authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri));
token = authresult.AccessToken;
Sometimes it fails with the following error, without ever bringing up the auth window:
authentication_ui_failed: The browser based authentication dialog failed to complete. Value does not fall within the expected range.
Occasionally, it does bring up the auth window but fails to redirect, producing:
"authentication_ui_failed: The browser based authentication dialog failed to complete. The system cannot locate the resource specified. (Exception from HRESULT: 0x800C0005)"
This uses a WinRT version of the library. A similar code using .NET version works great from the console app:
// .NET, console app. Works great
TokenCache TC = new TokenCache();
authContext = new AuthenticationContext(authority, TC);
var authresult = authContext.AcquireToken(resourceUri, clientID, new Uri(redirectUri));
token = authresult.AccessToken;

This is typically caused by the sandboxing of store and UWP apps. At a minimum, the redirect uri of the app should match the one assigned by the runtime - see the windows store sample on github.com/azuread. Other things that might impact the behavior are privacy settings on the box, use of local network without asking for the correct capabilities..: all the restrictions that apply to windows store apps will apply to the use of ADAL as well.
Also: can I ask you why you are passing a custom cache to the app? That's not usual for apps running on sandboxes environments like the windows store apps.

Related

Can't call Graph API calendars from a daemon application

I am new to the Graph API and would like to call my outlook calendars with the event schedules from a daemon application.
When I login to Microsoft account using the email I use to login to Azure I can see my calendar fine and I can also call the Web API using the Graph Explorer.
E.g. the Graph Explorer call:
https://graph.microsoft.com/v1.0/me/calendars
returns my calendar events fine when I am logged in with my Microsoft account.
Now, I would like to be able to access the same API using a service application i.e. without the user login prompt. So I went to the Azure portal, created and registered a new application, gave it Calendar.Read API permission with the administrator's consent and downloaded a quickstart daemon app which makes
await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users", result.AccessToken, Display);
call which works i.e. it returns a user so that I can see that the
"userPrincipalName": "XYZ#<formattedemail>.onmicrosoft.com"
which is not what the Graph Explorer call returns. The Graph explorer call:
https://graph.microsoft.com/v1.0/users
and returns "userPrincipalName": "myactualemail"
So basically when I make the Graph Explorer call:
https://graph.microsoft.com/v1.0/me/calendars
it returns the calendars' result which is correct.
However, an equivalent daemon API call
await apiCaller.CallWebApiAndProcessResultASync($"{config.ApiUrl}v1.0/users/f5a1a942-f9e4-460b-9c6c-16f45045548f/calendars", result.AccessToken, Display);
returns:
Failed to call the web API: NotFound
Content: {"error":{"code":"ResourceNotFound","message":"Resource could not be discovered.","innerError":{"date":"2021-12-26T16:46:35","request-id":"67ef50e4-bec6-48ae-9e45-7765436d1345","client-request-id":"67ef50e4-bec6-48ae-9e45-7765436d1345"}}}
I suspect that the issue is in the userPrincipalName mismatch between the Graph Explorer and the daemon application, but I am failing to find a solution to this.
Also note that a normal ASP.NET Core sample which requires manual user login works ok. The issue is only with the daemon application.
There is no "me" in your case, so you need to use https://graph.microsoft.com/v1.0/users/user#domain.demo/calendars url.
When you used Graph Explorer to test the api, you've signed in the website, so /me/calendars contained in the request can know who is me and then return correct data to you.
Come back to your daemon app, we usually use client credential flow to gain the access token/credential to call the api in the daemon so that we don't need to let user sign in and then call the api, this flow makes the app itself can call microsoft graph api. But using this flow will lead to the issue that you can't use me any more because you never signed in yourself, so we should use /users/userPrincipalName/calendars instead.
Then come to the programming module, microsoft provides graph SDK for calling api, this is what you can also see in the api document. You can refer to this document to learn more details about how to use client credential flow with graph SDK. You can also copy my code below.
using Azure.Identity;
using Microsoft.Graph;
public IActionResult Privacy()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "your_tenant_name.onmicrosoft.com";
var clientId = "azure_ad_app_client_id";
var clientSecret = "client_secret";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var res = graphClient.Users["your_user_id_which_looks_like_xxxx-xxx-xxx-xxxx-xxxxxx"].Calendars.Request().GetAsync().Result;
return View();
}
By the way, if you're not familiar with the flows, you may take a look at my this answer.
I was able to kind of resolve this issue after chatting with the Azure tech guy. It turned out that my Azure account was considered a personal account. And the reason for this apparently was because I was using a personal #yahoo.com email to setup up the Azure account first place. Because of this they would apparently not allow me to purchase o365 and license it. So I had to create a new account with the amazon default domain for S3 - awsapps.com, which I took from my AWS S3 subscription. Then I had to run through a whole process of creating a new email in Azure from my existing S3 custom domain.
After the email was created I was able to purchase o365 basic license (trial version for now) and then login to Azure using a new email. o365 purchase gave me access to outlook and then recreating a new daemon application from the quickstart with the new credentials just worked.
I don't know if it makes sense what I had done as it sounds awfully convoluted. But it seems to work in the end.

Is there a way to use the authentication retrieved from Microsoft Teams (via TeamsFX) and use it for an internal web API?

I am currently working on a Tab application for Microsoft Teams and have stumbled upon the extension called TeamsFx which enables easy retrieval of an authentication token from Teams without the need of prompting the user every time. Likewise, I used the Teams Toolkit extension for Visual Studio to get started. The project is implemented with .NET 5.0.
What I need, is to use the same authentication token retrieved from Teams inside the tab to then access a web API that is implemented in the same project. I have spent two full days trying to work this issue out with pretty much no result. I can retrieve an authentication token via TeamsFx, but simply using this as the bearer auth token in my web api did nothing but it telling me that the signature was wrong.
I have tried adding something like this to my Startup class:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
,but then it threw an exception because the TeamsFx library has already done the same with this call:
services.AddTeamsFxSimpleAuth(Configuration);
In previous Web API projects I have been able to set up Azure AD authentication to the site, and pretty seemlessly add the [Authorize] tag to any WebAPI classes and it just worked.
I hope that someone can save me from my own insanity ;D
Best,
//F_
I think this is the same question which created in Github, it is already answered and got the solution.
https://github.com/OfficeDev/TeamsFx/issues/1363
Since we only got JavaScript version of microsoft/teamsfx sdk, so we including webpack to wrapper it and using JSRuntime to trigger it in Blazor.
The webpack source code is in JS/src/index.js, you could add these code to get SSO token
export async function getSsoToken() {
var credential = new TeamsUserCredential();
var token = await credential.getToken("");
return token;
}
Then under JS folder , run 'npm install' and 'npm run build' to webpack the code and refresh the webpack file (wwwroot/teamsfx.js).
Finally you could using JSRuntime trigger the new func you just added in TeamsFx.cs.
await jsRuntime.InvokeAsync<AccessToken>("TeamsFx.getSsoToken");

windows Authorization in ReactJS and .NET Core API

We have a new application which is having ReactJS as front end and back end is .NET Core API.
A requirement is to authorize the windows logon user with respect to Active Directory.
The .NET Core API will be doing the Authorization part.
We have used the following code in .NET Core API but it is returning the ID under which the App-Pool of .NET Core API is running. We tried setting the API on Windows Authentication enabled but it did not work as well.
dynamic userIdentity = WindowsIdentity.GetCurrent();
dynamic userPrincipal = new WindowsPrincipal(userIdentity);
string Admin = _configuration["AppUserRoles:Admin"];
result = userPrincipal.IsInRole(Admin);
I have changed the code to the following:
dynamic userIdentity = WindowsIdentity("UserID");
dynamic userPrincipal = new WindowsPrincipal(userIdentity);
string Admin = _configuration["AppUserRoles:Admin"];
result = userPrincipal.IsInRole(Admin);
We need to pass the the UserID from ReactJS to the API Layer.
In ReactJS I have tried the following:
var path = require('path');
var userName = process.env['USERPROFILE'].split(path.sep)[2];
var loginId = path.join("domainName",userName);
But this is not working in ReactJS.
Is there a way we can fetch the Windows Logon ID in React JS and pass it to the API layer for authorization?
We were able to get this done by the following approach:
under IIS we hosted the website as follows:
Added a website ReactJSWeb.
i. Added .NETCore virtual directory under the ReactJS website.
Both Main website and Virtual directory had Authentication set as Windows Authentication Enabled.
In .NET Core API - authentication module we added a Attribute [Authorize] on the class and added the following code in the method:
using Microsoft.AspNetCore.Authorization;
dynamic userIdentity = WindowsIdentity(**User.Identity.Name**);
dynamic userPrincipal = new WindowsPrincipal(userIdentity);
string Admin = _configuration["AppUserRoles:Admin"];
result = userPrincipal.IsInRole(Admin);
This worked and we are now able to do the Authorization properly based on the Active Directory security group the user is part of.
Is it possible to share what you did to have client authenticated following that approach? I'm getting crazy with a similar scenario like yours...
I very appreciate that.

OpenIdConnectAuthenticationNotifications.AuthorizationCodeReceived event not firing

using this sample:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
This works as expected when running it locally
But when we deploy it (azure web app), it still authenticates, but the OpenIdConnectAuthenticationNotifications.AuthorizationCodeReceived event is not firing.
This the code.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}
This is a problem because it requires caching of the token to make an outbound call.
Since it doesn’t have it, it throws.
There was an issue that caused this related to a trailing slash after the redir url but we’ve already tried that.
So two questions…
1) Under what conditions would event get fired and why would this work when running locally? According to the docs it should be "Invoked after security token validation if an authorization code is present in the protocol message."
2) What is the best way to debug this? Not clear on what to look for here.
1) Under what conditions would event get fired and why would this work when running locally? According to the docs it should be "Invoked after security token validation if an authorization code is present in the protocol message."
As the document point, this event will fire when the web app verify the authorization code is present in the protocol message.
2) What is the best way to debug this? Not clear on what to look for here.
There are many reason may cause the exception when you call the request with the access_token. For example, based on the code you were using the NaiveSessionCache which persist the token using the Sesstion object. It means that you may also get the exception when you deploy the web app with multiple instance. To trouble shoot this issue, I suggest that you remote debug the project to find the root cause. For remote debug, you can refer the document below:
Introduction to Remote Debugging on Azure Web Sites

Get Google SpreadsheetService using OAuth

I'm developing GWTP project, and below scenarios are tested successfully in my local development mode :
Get authenticated and authorized by OpenID and OAuth
Save GoogleOAuthParameters object into HttpSession.
Another action handler reuses the GoogleOAuthParameters stored in session to get SpreadsheetService object.
Use SpreadsheetService to manipulate spread sheets in GDoc.
However, when being deployed to App Engine, nothing can be read from GDoc, and no error/warning also, and returned list is always empty.
spreadsheetService = new SpreadsheetService("test");
GoogleOAuthParameters oauthParameters = (GoogleOAuthParameters)sessionProvider.get().getAttribute(HttpSessionProvider.PARAM_OAUTH_PARAMETERS);
spreadsheetService.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
oauthParameters.setScope(SCOPE_SPREADSHEET);
If I clearly use username/password as below when initializing SpreadsheetService, I can retrieve data from GDoc.
SpreadsheetService sService = new SpreadsheetService("test");
sService.setUserCredentials("username", "password");
I'm using App Engine SDK 1.6.6, and gdata-spreadsheet-3.0.
Please advise whether anything I did wrongly.
Thanks!
The documentation is for .Net, not Java. If you want to develop in Java, I found a solution here: Sharing authentication/token between Android Google Client API and SpreadSheet API 3.0
String token = GoogleAuthUtil.getToken(
this,
this.accountName,
this.scopes);
SpreadsheetService service = new SpreadsheetService("my-service-name");
service.setProtocolVersion(SpreadsheetService.Versions.V3);
service.setHeader("Authorization", "Bearer " + token);
Note that this does not work for setting the token and results in a 401 for me:
// BAD CODE
service.setUserToken(token);
// BAD CODE
The documentation has complete samples in Java showing how to perform authentication with all supported mechanisms. The recommendation is to use OAuth 2.0 which is explained at:
https://developers.google.com/google-apps/spreadsheets/#performing_oauth_20

Resources