Xamarin OAuth2Authenticator Microsoft Account returns malformed JWT token for Azure App Service - xamarin

I am building a Xamarin.Forms app to learn the framework and working on the authentication services while using an Azure App Service for an API (also new to me). And trying to authenticate against a Microsoft account (Outlook.com).
I am using the Xamarin.Auth OAuth2Authenticator class and it is returning a very peculiar, malformed JWT token. This has been driving me nuts for days and finally decided to turn to where the experts are.
I have an IAuthService interface that will be used by the platform apps to build out the authentication service
public interface IAuthService
{
Task SignInAsync(string clientId,
Uri authUrl,
Uri callbackUrl,
Action<string> tokenCallback,
Action<string> errorCallback);
}
Said service (for iOS) is built out as follows (shortened for brevity):
public class AuthService : IAuthService
{
public async Task SignInAsync(string clientId, Uri authUrl, Uri callbackUrl, Action<string> tokenCallback, Action<string> errorCallback)
{
var auth = new OAuth2Authenticator(clientId, "openid", authUrl, callbackUrl);
auth.AllowCancel = true;
var controller = auth.GetUI();
await UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewControllerAsync(controller, true);
auth.Completed += (s, e) =>
{
controller.DismissViewController(true, null);
if (e.Account != null && e.IsAuthenticated)
{
Console.WriteLine(e.Account.Properties["access_token"]);
tokenCallback?.Invoke(e.Account.Properties["access_token"]);
}
else
{
errorCallback?.Invoke("Not authenticated");
}
};
...
}
}
So I instantiate a new instance of the OAuth2Authenticator class, specifying the scope as "openid"; the MS authorize endpoint requires a scope.
When it hits the console writeline I can see a value came back... but it's not like any JWT token I have seen before.
EwB4A8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAWPAOehpFaoAKb8Kz67ZZzgzBS3KUtHGZri2sbgIJfA5xZYDv5K417HIz2P+ggUeB/gFMxRfXH1Hd1qT90bfo6skGpIc/K2vDgBoRY0VnlA9nnCyct9B2tSaNQn3hZjPOiOchmSCJxrUMILGKdKy4kxxn5qFlTXAy0hWIQjHXcwGeKXDm1w3wY6x8xsmBxNNXor9FluuUXNNTtu4iP6s424JwIiJ7HCyu6ftORXCfIlemRSv5hcHLa1MXS9vUq95lRc08S05Ek7IiUfMiAnYbrqwD7H+vheAtfDc9kYleebyxlFl6gpVKmv43DV2yYgYIqgqswO6ktJ6Gar4zmqUYUIDZgAACGWNlS0Ln+wWSAJC2apVPWWIsKPobIL0uBImdORjOWvFOnLtKhQfCnngoo1Tw1UItqo5FRj1f/KWj3if/DPgWaQx5Bf4tbqCjuuOdEkR9r/Ru1v/ccjrg2oqp0hicWwIoSaQm2JHgnrrCQ1cfcvXhAuVlAo9tKyqW/dCehdz7NRpQbNtmLvba+PjWWYEcDROJJSwTRqNTGkwiwzNhw8p/Zlf7G51F205S4vDZob1MsWythkrUJAjA6MUJy4wZ5B/8ChF7J3WRSTapjr+6mNgvgvhcflGo6GoEID24aSDL6h9QGPylk6zfghksweu9/AmSMO4BKwLDVSr04BJj1n1rfKsadUBqWUQMaXFGu+OfGbOCm6E5zLSJyO2JKbcsI468gb0/vC6FYFJOzp56GXD5brQtKNtu9urtge02kOwaGlHsK2I28BMdCRVFYJI9kEiqhqr342ZDlob7mpBCoNDk1uLLH2MPDAW9NOpq+V0bab+WawINAjl1GY/obL3zRsVNMoAszFSfdbbWS/KDbx6rw5bUPMC37s6LTbECkXHhqeqDlNQs4G9BccfiJNI5CQa+QPmaRNOBKhD2K97Z9fXmAFY155WzTPoIVKupxkPXo0zp/9vOc/HHEtMlkoUUNzxX5Q7T8awfN/7F4IfShXQKEVLaIStdx5istw7rxfuv1v/U+EMj4fmYUW9sNG/5irVyGAAOVvvPNkavLnl+NaKYysvAxYVPlrj+zJIDi5C91MmRhiTfH/Lgyq9Mlr/FaLIa/Ow6rCIjO4oBZSl9dXwLxFI4oQC
It's not encoded/decoded.
The authUrl I am using is
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
If I attempted to use the v1.0 of the authorize endpoint, the phone OS appears to throw an error to me stating Authentication Error - Invalid state from server. Possible forgery!
I have set up an Azure App Service to act as my API and if I hit my app login url directly it gives me back a well formed token which I then could use to hit the api with no problem. So that tells me that my cliend_id are correct and everything should work.
So I am doing it wrong somewhere. I don't know if I trust the OAuth2Authenticator class from Xamarin when using MS accounts. But documentation is old and/or lacking. I feel like it's going to be an easy answer but I am going cross-eyed trying to interpret MS/Xamarin/Googled documentation.

Related

What is the botframework security model?

I am exploring the Microsoft Bot Builder SDK to create a chat bot that integrates with MS Teams. Most of the provided samples do not have any authentication mechanisms and the samples that reference OAuth seem to do so for allowing the bot to access a resource using the on-behalf-of flow. Is correct way to think of the security model is that the bot should be considered public and any non-public information accessed is done from the context of the calling user?
The Bot Framework has three kinds of authentication/authorization to consider:
Bot auth - Microsoft app ID and password
Client auth - Direct Line secret/token, or various mechanisms for other channels
User auth - OAuth cards/prompts/tokens
Unfortunately there's some inconsistency in the documentation about which is which, but I've just raised an issue about that here: https://github.com/MicrosoftDocs/bot-docs/issues/1745
In any case, there's no need to think of all bots as "public." The Bot Builder SDK authenticates both incoming messages and outgoing messages using its app ID and password. This means any unauthorized messages sent to the bot's endpoint will be rejected, and no other bot can impersonate yours.
In general you should have the user sign in if you want the bot to access secure information on the user's behalf. But since you mentioned wanting to restrict bot access to specific tenants, I can briefly explain how to do that. You can find middleware here that does it in C#, and here's a modified version of the code that I think improves on it by using a hash set instead of a dictionary:
public class TeamsTenantFilteringMiddleware : IMiddleware
{
private readonly HashSet<string> tenantMap;
public TeamsTenantFilteringMiddleware(IEnumerable<string> allowedTenantIds)
{
if (allowedTenantIds == null)
{
throw new ArgumentNullException(nameof(allowedTenantIds));
}
this.tenantMap = new HashSet<string>(allowedTenantIds);
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
{
await next(cancellationToken).ConfigureAwait(false);
return;
}
TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
string tenantId = teamsChannelData?.Tenant?.Id;
if (string.IsNullOrEmpty(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id is missing.");
}
if (!this.tenantMap.Contains(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
}
await next(cancellationToken).ConfigureAwait(false);
}
}

How to flow user Consent for a Web API to access MS Graph user profile in AAD V2 end point with MSAL library

I'm trying to build a feature where a client application retrieves the graph resources via WebAPI layer. The scenario has following applications:
Angular5 Client application
ASP.Net Core Web API
The Angular5 client application uses MSAL to authenticate against application (resisted as Converged application via apps.dev.microsoft.com registration application; AAD v2 endpoint).
The authentication flow defines the Web API as scope while login or getting access token
constructor() {
var logger = new Msal.Logger((logLevel, message, piiEnabled) =>
{
console.log(message);
},
{ level: Msal.LogLevel.Verbose, correlationId: '12345' });
this.app = new Msal.UserAgentApplication(
CONFIGSETTINGS.clientId,
null,
this.authCallback,
{
redirectUri: window.location.origin,
cacheLocation: 'localStorage',
logger: logger
}
);
}
public getAPIAccessToken() {
return this.app.acquireTokenSilent(CONFIGSETTINGS.scopes).then(
accessToken => {
return accessToken;
},
error => {
return this.app.acquireTokenSilent(CONFIGSETTINGS.scopes).then(
accessToken => {
return accessToken;
},
err => {
console.error(err);
}
);
}
);
}
Here scope is defined as scopes: ['api://<<guid of application>>/readAccess']. This is the exact value which was generated when I've registered the Web API in registeration portal. Also, the client application id is added as Pre-authorized applications .
The Web API layer (built in dotnet core -- and uses JwtBearer to validate the authentication), defines the API which internally fetches the graph resources (using HttpClient). To get the access token, I've used following code
public async Task<string> GetAccesToken(string resourceName)
{
var userAssertion = this.GetUserAssertion();
string upn = GetLoggedInUpn();
var userTokenCache = new SessionTokenCache(upn, new Microsoft.Extensions.Caching.Memory.MemoryCache(new MemoryCacheOptions())).GetCacheInstance();
string msGraphScope = "https://graph.microsoft.com/User.Read";
string authority = string.Format("https://login.microsoftonline.com/{0}/v2.0", this.authConfig.TenantId);
ConfidentialClientApplication clientApplication = new ConfidentialClientApplication(this.authConfig.ClientId, authority, new ClientCredential(this.authConfig.AppKey), userTokenCache, null);
var result = await clientApplication.AcquireTokenOnBehalfOfAsync(new string[] { msGraphScope }, userAssertion);
return result != null ? result.AccessToken : null;
}
private UserAssertion GetUserAssertion()
{
string token = this.httpContextAccessor.HttpContext.Request.Headers["Authorization"];
string upn = GetLoggedInUpn();
if (token.StartsWith("Bearer", true, CultureInfo.InvariantCulture))
{
token = token.Trim().Substring("Bearer".Length).Trim();
return new UserAssertion(token, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
else
{
throw new Exception($"ApiAuthService.GetUserAssertion() failed: Invalid Authorization token");
}
}
Note here, The method AcquireTokenOnBehalfOfAsync is used to get the access token using graph scope. However it throws the following exception:
AADSTS65001: The user or administrator has not consented to use the application with ID '<>' named '<>'. Send an interactive authorization request for this user and resource.
I'm not sure why the of-behalf flow for AAD v2 is not working even when client application uses the Web API as scope while fetching access token and Web API registers the client application as the pre-authorized application.
Note - I've tried using the other methods of ConfidentialClientApplication but even those did not work.
Can someone please point out how the above flow can work without providing the admin consent on Web API?
I've been trying to figure this out for weeks! My solution isn't great (it requires the user to go through the consent process again for the Web API), but I'm not sure that's entirely unexpected. After all, either the Admin has to give consent for the Web API to access the graph for the user, or the user has to give consent.
Anyway, the key was getting consent from the user, which of course the Web API can't do since it has no UI. However, ConfidentialClientApplication will tell you the URL that the user has to visit with GetAuthorizationRequestUrlAsync.
Here's a snippet of the code that I used to get it working (I'm leaving out all the details of propagating the url back to the webapp, but you can check out https://github.com/rlittletht/msal-s2s-ref for a working example.)
async Task<string> GetAuthenticationUrlForConsent(ConfidentialClientApplication cca, string []graphScopes)
{
// if this throws, just let it throw
Uri uri = await cca.GetAuthorizationRequestUrlAsync(graphScopes, "", null);
return uri.AbsoluteUri;
}
async Task<string> GetAccessTokenForGraph()
{
// (be sure to use the redirectUri here that matches the Web platform
// that you added to your WebApi
ConfidentialClientApplication cca =
new ConfidentialClientApplication(Startup.clientId,
"http://localhost/webapisvc/auth.aspx",
new ClientCredential(Startup.appKey), null, null);
string[] graphScopes = {"https://graph.microsoft.com/.default"};
UserAssertion userAssertion = GetUserAssertion();
AuthenticationResult authResult = null;
try
{
authResult = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion);
}
catch (Exception exc)
{
if (exc is Microsoft.Identity.Client.MsalUiRequiredException
|| exc.InnerException is Microsoft.Identity.Client.MsalUiRequiredException)
{
// We failed because we don't have consent from the user -- even
// though they consented for the WebApp application to access
// the graph, they also need to consent to this WebApi to grant permission
string sUrl = await GetAuthenticationUrlForConsent(cca, graphScopes);
// you will need to implement this exception and handle it in the callers
throw new WebApiExceptionNeedConsent(sUrl, "WebApi does not have consent from the user to access the graph on behalf of the user", exc);
}
// otherwise, just rethrow
throw;
}
return authResult.AccessToken;
}
One of the things that I don't like about my solution is that it requires that I add a "Web" platform to my WebApi for the sole purpose of being able to give it a redirectUri when I create the ConfidentialClientApplication. I wish there was some way to just launch the consent workflow, get the user consent, and then just terminate the flow (since I don't need a token to be returned to me -- all I want is consent to be granted).
But, I'm willing to live with the extra clunky step since it actually gets consent granted and now the API can call the graph on behalf of the user.
If someone has a better, cleaner, solution, PLEASE let us know! This was incredibly frustrating to research.

Authenticating a Xamarin Android app using Azure Active Directory fails with 401 Unauthorzed

I am trying to Authenticate a Xamarin Android app using Azure Active Directory by following article here:
https://blog.xamarin.com/authenticate-xamarin-mobile-apps-using-azure-active-directory/
I have registered a native application with AAD; note that i havent given it any additional permissions beyond creating it.
Then i use the below code to authenticate the APP with AAD
button.Click += async (sender, args) =>
{
var authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.Count > 0)
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().GetEnumerator().Current.Authority);
authResult = await authContext.AcquireTokenAsync(graphResourceUri, clientId, returnUri, new PlatformParameters(this));
SetContentView(Resource.Layout.Main);
doGET("https://management.azure.com/subscriptions/{subscription-id}/resourceGroups/OPSLABRG/providers/Microsoft.Compute/virtualMachines/LABVM?api-version=2015-08-01", authResult.AccessToken);
};
private string doGET(string URI, String token)
{
Uri uri = new Uri(String.Format(URI));
// Create the request
var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "GET";
// Get the response
HttpWebResponse httpResponse = null;
try
{
httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
}
catch (Exception ex)
{
Toast.MakeText(this, "Error from : " + uri + ": " + ex.Message, ToastLength.Long).Show();
return null;
}
}
This seems to be getting a token when using a Work account.
Using a valid hotmail account throws error A Bad Request was received.
However the main problem is when i try to retrieve VM details using REST.
the REST GET method fails with 401 Unauthorized error even when using the Work account.
I am not sure if the code is lacking something or if i need to give some additional permissions for the App. This needs to be able to support authenticating users from other tenants to get VM details.
Any guidance is appreciated.
note that i havent given it any additional permissions beyond creating
it.
This is the problem here.
In order for you to call the Azure Management API https://management.azure.com/, you must first register your application to have permissions to call this API.
You can do that as a part of your app registration like so:
Only at that point, will your app be authorized to call ARM, and your calls should start to work.
According to your description, I checked this issue on my side. As Shawn Tabrizi mentioned that you need to assign the delegated permission for accessing ARM Rest API. Here is my code snippet, you could refer to it:
var context = new AuthenticationContext($"https://login.windows.net/{tenantId}");
result = await context.AcquireTokenAsync(
"https://management.azure.com/"
, clientId, new Uri("{redirectUrl}"), platformParameter);
I would recommend you using Fiddler or Postman to simulate the request against ARM with the access_token to narrow this issue. If any errors, you could check the detailed response for troubleshooting the cause.
Here is my test for retrieving the basic information of my Azure VM:
Additionally, you could leverage jwt.io for decoding your access_token and check the related properties (e.g. aud, iss, etc.) as follows to narrow this issue.

How to use auth token in Cortana Connected account scenario with graph api sdk

I am trying to follow the Cortana connected account example provided here. The default example uses live api which is working fine; however, I wanted to use Graph api sdk. Trying to use the same auth token (which is sent by Cortana as one of the entities) with Graph api is giving this error:
Microsoft.Graph.ServiceException: 'Code: InvalidAuthenticationToken
Message: CompactToken parsing failed with error code: -2147184118
The relevant code is:
var graphserviceClient = new GraphServiceClient(new AzureAuthenticationProvider(authAccessToken));
The AzureAuthenticationProvider class is:
public class AzureAuthenticationProvider : IAuthenticationProvider
{
string _accessToken;
public AzureAuthenticationProvider(string accessToken)
{
_accessToken = accessToken;
}
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Add("Authorization", "Bearer " + _accessToken);
}
}
How can I use the auth token sent by Cortana to my bot to perform Graph queries using the Graph Sdk?
If you are trying to get a token for graph resource from Cortana, the Token URL in Connected Account should have a query parameter called resource=https://graph.microsoft.com/ Also, to verify the validity of the token, try decoding it using JWT.io. You should be able to see graph as the audience in your token.

PlatformPlugin exception getting app-only token using MSAL in ASP.NET Core

I'm trying to create an ASP.NET Core app that uses MSAL for the client credentials flow.
I get the following exception when trying to get an access token:
NullReferenceException: Object reference not set to an instance of an object.
Microsoft.Identity.Client.Internal.PlatformPlugin.LoadPlatformSpecificAssembly()
Authentication is modeled after this sample: active-directory-dotnet-webapp-openidconnect-aspnetcore-v2
Authorization is modeled after this sample: active-directory-dotnet-daemon-v2
My app is registered with the Mail.Read Application permission. Here's the code that throws the exception:
string authority = $"https://login.microsoftonline.com/{ tenantId }/v2.0";
ConfidentialClientApplication client = new ConfidentialClientApplication(
authority,
appId,
redirectUri,
new ClientCredential(appSecret),
null);//new TokenCache());
AuthenticationResult authResult = await client.AcquireTokenForClient(
new string[] { "Mail.Read" }, null);
return authResult.Token;
Also tried with "https://graph.microsoft.com/.default" scope, as in the daemon sample.
Answering this question. I opened this as an issue on the GitHub repo. It was closed with the following response:
this problem will not occur with the next release as we will move to a single binary.

Resources