How to consume Graph API with a ADAL token? - azure

in my Xamarin.forms project, I use ADAL (Microsoft.IdentityModel.Clients.ActiveDirectory) to authenticate on the Azure portal (Auth 1.0 endpoint). That part work great, but I need to get the security group of the user. So I use this code and passing the token received with ADAL:
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/memberOf");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
HttpResponseMessage response = await client.SendAsync(message);
string responseString = await response.Content.ReadAsStringAsync();
I always got StatusCode: 401, ReasonPhrase: 'Unauthorized'.
In my azure AD app registration, I add the Graph API and these permissions:
I think I miss something. Any idea?
----- EDIT 1 ---
Here my payload. I changed it for a picture for lisibility. I don't know how to post json here
----- EDIT 2 ---
OH! I see. I think I need to understand more the Azure Login process. For now I follow an example of ADAL and Azure that let my log and use some function in my backend. So the login process use:
var authContext = new AuthenticationContext(authority); var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
Where authority = https://login.microsoftonline.com/mysite.com, ResourceID is my backend app ID and clientID is my native app ID. So Shawn is correct, I do not use the Graph.microsoft.com to get the token. Do we have another way to achieve all that? The need of using Graph is only to get the AD group the user has to adjust permission inside the app.

You are correct in your comment. If you add a new permission to your application, you must ask the user to re-consent to the app.
You can force re-consent through ADAL by setting the PromptBehavior to Always:
platformParams.PromptBehavior = PromptBehavior.Always
Or you can simply modify your Login URL to force it, by adding the query string:
&prompt=consent
In terms of building an app to help overcome this problem, if you think your app will be changing permissions after release, you can integrate logic which detects Unauthorized, and then sends the user to re-consent.
Another option is for your app to track changes which may require a new consent prompt, and detect when the user uses this new version of your application the first time, and asks them to consent.
In our new App Model V2, we support the concept of Incremental and Dynamic Consent, which should get rid of this problem all together for you.

Related

authority_not_in_valid_list: 'authority' is not in the list of valid addresses

I am trying to call a Authenticated API from my client app. However, when making AcquireTokenAsync, I get following error "authority_not_in_valid_list: 'authority' is not in the list of valid addresses"
here is my code snippet:
resourceUrl = "https://myApiEndPoint.com";
var clientCredential =
new ClientCredential( myClientAppId, myClientSecretKey );
// myClientAppId and myClientSecretKey are the values from Azure Portal
var authContext =
new AuthenticationContext( "https://my_authority/myApiEndPoint");
return await authContext.AcquireTokenAsync( resourceUrl, clientCredential );
In my azure Portal for my client Id of app, I have granted delegated permission to access https://myApiEndPOint.com api.
Any thoughts on what could be causing this issue and what does it mean by not in valid list?
I understand that:
you created your application in the Azure portal, and therefore the authority is the Azure AD endpoint. Therefore the authority is probably https://login.microsoftonline.com/common? Or do you have good reasons to use "https://my_authority" ?
you have granted delegated permissions to access the API. This means that your application will access the API in the name of the user. However the AcquireTokenAsync method that you use is using the "ClientCredential" flow (meaning with an application secret)
You probably rather want to use another override passing the resourceUri, the clientId, ...
If this is your use case, I suggest you have a look to the active-directory-dotnet-webapi-onbehalfof sample (See here)

Authenticate App Services backend using Microsoft Graph token?

Edit:
I have added the "id_token" but still get an "Unauthorized" response.
Here is my login code:
PublicClientApplication myApp = new PublicClientApplication("My-AppID-From-App-Registration-Portal");
string[] scopes = new string[] { "User.Read" };
AuthenticationResult authenticationResult = await myApp.AcquireTokenAsync(scopes).ConfigureAwait(false);
JObject payload = new JObject();
payload["access_token"] = authenticationResult.AccessToken;
payload["id_token"] = authenticationResult.IdToken;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, payload);
Original Post:
Is it possible to authenticate to a App Services backend using the token retrieved from Microsoft Graph?
I have already tried using this token and calling LoginAsync() with AzureActiveDirectory as the provider, this doesn't work.
JObject payload = new JObject();
payload["access_token"] = GraphAuthenticationHelper.TokenForUser;
user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Is this possible?
UPDATE: In my original answer, I said you cannot do this. But in reality, you can do this but it's a dangerous thing to do since anyone with a valid Microsoft Graph token could theoretically access your APIs. Before I walk you down that path, let me describe the "right" way to access the Microsoft Graph on behalf of your end user.
The right way to do this is to use the on-behalf-of flow in the mobile backend code to exchange the user's ID token for a Microsoft Graph token. The flow looks like the following:
Client initiates a login with AAD using MSAL and sets the resource to the mobile backend (not the Graph). The result should be a set of tokens.
Client uses the mobile SDK to do a login with BOTH the access_token AND the id_token from #1.
Example code:
JObject payload = new JObject();
payload["access_token"] = {access_token.from.msal};
payload["id_token"] = {id_token.from.msal};
var user = await MobileService.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
payload);
The backend code exchanges the user's ID token (from the x-ms-token-aad-id-token request header) for a graph token. This token exchange is known as "on-behalf-of" and is documented here. I think this can be done using ADAL or MSAL libraries, but I wasn't able to find documentation. It's also simple enough that you could implement the HTTP protocol directly without too much trouble.
The backend uses the newly acquired MS Graph token and makes the graph API call.
You can also cache the graph token that you acquire on the backend so that each API call doesn't require more AAD API calls to do token exchange.
I think no ,please refer to document : https://learn.microsoft.com/en-us/azure/app-service-mobile/app-service-mobile-dotnet-how-to-use-client-library#a-nameauthenticationaauthenticate-users
Replace INSERT-RESOURCE-ID-HERE with the client ID for your mobile app backend. You can obtain the client ID from the Advanced tab under Azure Active Directory Settings in the portal.
The audience of the access token should be the client ID for your mobile app backend . So if resource is https://graph.microsoft.com/(aud claim in access token) , then Client-managed authentication won't work .

Simple Directory Lookup in Azure Active Directory

I am writing a simple desktop application that needs to retrieve some basic properties about a user from Microsoft’ directory. Specifically:
I am writing a single tenant native LOB application.
The application runs on my desktop.
The application runs as my logged on domain account.
The organization' domain accounts are synced to AAD.
I am not trying to secure a native web app or a Web API or anything like that. I do not need users to sign in.
I have email addresses of folks in my organization from an external event management tool. I need to lookup the AAD account profile data (address book info - specifically job title) from AAD based on the email address. I will only be reading AAD data.
So far, I have done the following:-
It appears that the Azure AD Graph API is the right way to fetch the profile information. In particular, the information is available at the endpoint: https://graph.windows.net/{tenant}/users/{email}?api-version=1.6
When registering the native application in AAD, no key was provided. So I don't have a client secret.
Looked at the sample in GitHub here: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-console. The instructions here seem to be wrong because no Keys section is available [see (2)].
Based on the sample above, I wrote a simple function. Code is below:
private static async Task PrintAADUserData(string email)
{
string clientId = "0a202b2c-6220-438d-9501-036d4e05037f";
Uri redirectUri = new Uri("http://localhost:4000");
string resource = "https://graph.windows.net/{tenant}";
string authority = "https://login.microsoftonline.com/{tenant}/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
string api = String.Format("https://graph.windows.net/{tenant}/users/{0}?api-version=1.6", email);
LOG.DebugFormat("Using API URL {0}", api);
// Create an HTTP client and add the token to the Authorization header
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(api);
string data = await response.Content.ReadAsStringAsync();
LOG.Debug(data);
}
Questions
The application when run was able to bring up the authentication page. Why do I need that? The application already runs as my domain account. Is an additional authentication necessary? If I were to run this application in Azure as a worker process, then I would not want to use my domain credentials.
The primary problem seems to be the resource URL which is wrong. What resource do I need to specify to access the Azure AD Graph API?
Thanks,
Vijai.
EDITS
Based on the comments from #Saca, the code and application has been edited.
Code
string clientId = ConfigurationManager.AppSettings["AADClientId"];
string clientSecret = ConfigurationManager.AppSettings["AADClientSecret"];
string appIdUri = ConfigurationManager.AppSettings["AADAppIdURI"];
string authEndpoint = ConfigurationManager.AppSettings["AADGraphAuthority"];
string graphEndpoint = ConfigurationManager.AppSettings["AADGraphEndpoint"];
AuthenticationContext authContext = new AuthenticationContext(authEndpoint, false);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.windows.net", new ClientCredential(clientId, clientSecret));
ExistingTokenWrapper wrapper = new ExistingTokenWrapper(authResult.AccessToken);
ActiveDirectoryClient client = new ActiveDirectoryClient(new Uri(graphEndpoint), async () => await wrapper.GetToken());
IUser user = client.Users.Where(_ => _.UserPrincipalName.Equals(email.ToLowerInvariant())).Take(1).ExecuteSingleAsync().Result;
App
Error
Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> Microsoft.Data.OData.ODataErrorException: Insufficient privileges to complete the operation. ---> System.Data.Services.Client.DataServiceQueryException: An error occurred while processing this request. ---> System.Data.Services.Client.DataServiceClientException: {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
It appears that despite giving the right permissions, the correct resource and being able to acquire a token, there is still something missing.
The key thing to consider here is if your application will be a headless client run from a secure server or desktop client run by users on their machines.
If the former, then your application is considered a confidential client and can be trusted with secrets, i.e. the keys. If this is your scenario, which is the scenario covered by the sample, then you need to use clientId and clientSecret.
The most likely reason you are not seeing a Keys section in the your application's Configure page is that, instead of selecting Web Application and/or Web API as per step #7 in the sample, you selected Native Client Application when first creating the application. This "type" can't be changed, so you'll need to create a new application.
If your scenario is the latter, then your application is considered a public client and can't be trusted with secrets, in which case, your only options is to prompt the user for credentials. Otherwise, even if your app has it's own authorization layer, it can easily be decompiled and the secret extracted and used.
Your resource URL is correct by the way.
Turns out the real issue was not with the code. I am not an AAD administrator. It appears that any application needing to perform authentication against AAD in our tenant needs to have permissions enabled by the AAD administrators. Once they enabled permissions for my application (and took ownership of the AAD registration as well), this started working.
Hope help some one that are using GraphClient:
var userPriNam = "johndoe#cloudalloc.com";
var userLookupTask = activeDirectoryClient.Users.Where(
user => user.UserPrincipalName.Equals(userPriNam, StringComparison.CurrentCultureIgnoreCase)).ExecuteSingleAsync();
User userJohnDoe = (User)await userLookupTask;
from https://www.simple-talk.com/cloud/security-and-compliance/azure-active-directory-part-5-graph-api/

Azure Active Directory Logout with ADAL library

I used the my Azure Active Directory to protect my web API and I create a native application in the Azure management portal. This native application is basically a MVC web application and I use the ADAL library to get the token and call the api with that token. The code I used to get the token is shown below:
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult ar = ac.AcquireToken(resourceID, clientID, redirectURI);
string accessToken = ar.AccessToken;
Now I need to logout and switch to another user but somehow the user credentials are remembered by the system. I clear the token cache in the authentication context and post logout api request as follows where *** is my tenant ID.
//Log out after api call
ac.TokenCache.Clear();
string requestUrl = "https://login.windows.net/***/oauth2/logout";
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await client.SendAsync(request);
The api call succeeds but the logout doesn't work.
What should I do to logout and switch to another user?
I don't think this would work. You would need to redirect the user to logout URL for logout to work.
Here's how you can create a logout URI:
https://login.microsoftonline.com/{0}/oauth2/logout?post_logout_redirect_uri={1}
Where:
{0} - Fully qualified name of your Azure Active Directory e.g. yourad.onmicrosoft.com or tenant id.
{1} - The URL of your application where a user must be redirected back after the logout is complete. This should be properly URL encoded.
If you goal is to sign in a s a different user, you don't strictly need to log out the first user from its session with Azure AD. You can pass PrompBehavior.Always in your AcquireToken call, so that you will be guaranteed to prompt the user with a clean credential gathering UX.
Note: if you want to wipe every trace of the first user from the app you can keep the cache cleanup code you have. ADAL allows you to keep tokens for multiple users tho, hence if your app as multi-user functions this might be useful - the catch is that if you do so, at every AcquireToken you'll have to also specify which user you want a token for or ADAL won't know which one to return. If you don't need multiple users at once, the cache cleanup + PromptBehavior.Always remains the easiest path.
You can do this for clear cache :
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();
mAuthContext.getCache().removeAll();

Custom authorization with Azure AD Authentication in OWIN Web API

We are using Azure AD authentication for one of our client application. We want to implement claims based authorization along with it.
Our application set up is Angular Based client app connecting with Web API (both client server secured using Azure AD Bearer Authentication). Server application is hosted using OWIN.
We need to provide custom authorization on server side. There is a provision in Azure AD for adding users and roles. However, that is not enough for us. Our user management is through AD & Security Groups. To gain access to application, users need to part of a base group and further rights (access particular section of application, edit a specific entity etc.) are assigned based on additional groups or given directly to users in the application. Essentially, not all users will be registered in the application and we may have to query the AD using graph API to check which all application specific groups they belong.
OWIN authentication and authorization model is based on Authentication Server and Resource server. We can separate them on need basis. However, in our case, we need to split the authentication and authorization. When the client presents the bearer token, we need to verify if the token is valid and then add claims to user profile. We also need to cache the user claims so that we do not hit the database frequently. (Our client app make multiple Web API calls in one user action.)
What is the location in Identity 2.0 where
I can verify the token &
insert application specific claims
If my entire application revolves around the user authorization and all queries need to be filtered on what data the user can access, which is a more suitable design pattern for the Web API application?
I believe what you're looking for are the Authentication and Authorization filters in the ASP.NET Web API 2.0 stack.
You can implement per-web method authorization by implementing System.Web.Http.Filters.IAuthorizationFilter on an attribute class, then decorate the web action methods of your service controller with that attribute. Web API 2.0 will select a method based on URL routing, notice that there is an attribute on that method implementing IAuthorizationFilter, and will call the ExecuteAuthorizationFilterAsync method on that attribute instance before calling the web method. Placing the authorization step before the web method invocation allows invalid requests to be discarded quickly, before getting into the heavy lifting of parameter binding.
The incoming token is validated by an IAuthenticationFilter implementation which executes before the authorization step.
Documentation and examples are extremely hard to find. Here's one of the few search results that are actually relevant: http://thegrumpycoder.com/post/105427070626/secure-web-services-with-web-api-and-sitecore
you can check if this helps...
UserProfile profile = new UserProfile(); //To deserialize the response stream (JSON)
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
AuthenticationResult result = null;
try
{
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
//use ClientID, ClientSecret
ClientCredential credential = new ClientCredential("b557ceed-xxxx-xxxx-xxxx-xxxxxxxbc240", "AXFxx//xxxxxxxxxxxxxjVFz4sqYm8NDAPEOLkU=");
result = authContext.AcquireTokenSilent("https://graph.windows.net", credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// AcquireTokenSilent may throw exception if the cache is empty. In that case, logout the user and make him login.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
"https://graph.windows.net/cdmsdev.onmicrosoft.com/groups/b40xxxx-14a8-xxxx-9559-xxxxxxca90c8/members/?api-version=1.6");
//Above grap API url is for getting list of users who belong to a specific group (with GUID b40xxxx-1....)
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var upn = ClaimsPrincipal.Current.Identity.Name;
string responseString = response.Content.ReadAsStringAsync().Result;
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
if (profile.Users.Contains(upn)) //check if the current user is in the list of users of the Admin group
return true;
}
}
catch (Exception e)
{
//handle authorization exception here
}
The graph API URL can be replaced with a function to check for membership of a specific group which will directly return a bool value instead of getting all users of that group.

Resources