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();
Related
I have an Azure Web App that authenticates a user which then navigates to a page where some Sharepoint documents are retrieved and displayed in the app.
Most of the time the application works fine, but ocassionally App Insights will highlight that Failed to acquire token silently as no token was found in the cache. Call method AcquireToken. Some users report issues from time to time on this page (it's inconsistent so it might happen a few times a day with a somewhat large user base). The problem is that currently the error isn't handled and I'm trying to figure out how to make the call to AcquireTokenAsync.
The following is the method that returns the token (or doesnt):
private async Task<string> GetUserAccessToken()
{
try
{
// Credentials for app
// _clientId and _clientSecret represent the app info - not shown here in code
ClientCredential credential = new ClientCredential(_clientId, _clientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = Request.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(Request.HttpContext.User);
AuthenticationContext authContext = new AuthenticationContext(_authority, cache);
// guid of the user currently logged into the app
string objectID = _userObjectId;
UserIdentifier userIdentifier = new UserIdentifier(objectID, UserIdentifierType.UniqueId);
string resource = "https://test.sharepoint.com";
AuthenticationResult result = await authContext.AcquireTokenSilentAsync(resource, credential, userIdentifier);
return result.AccessToken;
}
catch (Exception ex)
{
throw ex;
}
}
If I understand the flow correctly, the web app here will request a token using it's own credentials on behalf of the user currently logged in. (Am I right in understanding this based on the method signature which states - Identifier of the user token is requested for. This parameter can be Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier.Any.)
Now when this fails, I would need to make a call to AcquireTokenAsync. There are a number of these methods available and I can't seem to find the one that will fulfill this requirement.
Before the suggestion comes, I can't use AcquireTokenAsync(resource, clientId, redirectUri,new PlatformParameters(PromptBehavior.Auto)); because the constructor on PlatformParameters has changed and requires an implementation of a ICustomWebUi and this flow isn't supported on .Net Core 3.1 as far as I'm aware which makes this unusable.
AcquireTokenAsync(resource, credentials) works and returns a token, however, when using that token I get a 401 Unauthorized when accessing the Sharepoint resources, most likely because the token is different and it is now requested on behalf of the application and not the user logged into the application (if I'm following this train of thought correctly...).
My question is - which method do I call? Is there something I would need to add before making the call to AcquireTokenAsync and if so, which of the 10 or so overloads should I use? I tried using AcquireTokenAsync(resource, credenetial, userAssertion) and passed in the AccessToken that I retrieved on the User logged in, but then I got Assertion failed signature validation or variations on that. If I understood correctly, the UserAssertion can be initialized with 1,2 or 3 parameters and I tried providing the AccessToken currently on the user that is logged in the app, but with no success.
Any help is greatly appreciated as I've been looking at this for two days now.
I spent more time investigating this, but none of the methods available would have worked in my case. The auth flow wasn't an on-behalf-of flow, but an auth-code flow. The link is to the newer MSAL library, but the concept is there. The application, a .net core web app, directs the user to sign in. When they sign in, an auth-code is passed into the response once they successfully authenticate.
The auth-code is then used to call AcquireTokenByAuthorizationCodeAsync(AuthCode, Uri, ClientCredential, UserIdentifier). This returns the valid access token that can be stored in the distributed token cache and then used to authenticate in order to access a given resource.
My biggest issue was that the error says you need to use AcquireTokenAsync to retrieve a new token. This is correct to a certain point, because in order to make any calls to any of the 14 or so methods you will need different bits of information, which will be dependent on the way you have setup your authentication flow in your application.
Because the application I worked on used auth code flow, I would need to get a new auth code. This would mean redirecting the user to login, capture the auth code in the response if the login was successful and then call the appropriate AcquireTokenAsync method that takes in an auth code as parameter along with app info, uri and so on.
To solve this, I used the information provided by the Microsoft Github page on Acquiring tokens with auth codes in web apps. Here I found samples on how auth flow is setup, but most importantly, how to trigger a new authentication flow if the user needs to be re-authenticated.
I wrapped the code that would throw the AdalSilentTokenAcquisitionException, catch the error and return a RedirectToAction.
return RedirectToAction("ActionName", "Controller", new RouteValues);
The above redirects the user to a given action, in a particular controller and passes through an object that can hold additional parameters. In my case it's a new { redirectUri = redirectUriString}, which is a string object that holds the URL the user would try to navigate this. I constructed this with a little method that uses the current HttpRequest to find the url the user was trying to get to.
Next, the controller that I had setup which responds to that redirect:
[HttpGet("/SignIn")]
public IActionResult SignIn([FromQuery(Name ="redirectUri")]string redirectUri)
{
return Challenge
(
new AuthenticationProperties { RedirectUri = WebUtility.UrlDecode(redirectUri) },
OpenIdConnectDefaults.AuthenticationScheme
);
}
Here, a Challenge is returned. The challenge triggers a call to the authentication flow that was setup in the Startup class. I think the entire flow here is that the method will send people to go through whatever is in that startup, which, in the case of the application I worked on, it prompts the user to sign in, captures the auth code, requests a new access token and once this is received and saved in the distributed token cache, the user is redirected to the redirectUri that I passed through.
I hope this helps or at least gives a starting point to anyone who might encounter a similar issue.
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.
I have an Azure mobile backend set up with easy auth for facebook and google authentication and it works as expected.
Every time a user signs in with any of the supported providers, I want to be able to verify if it's a new user or not (e-mail not in database), without make an additional call from client. Is this possible?
Every time a user signs in with any of the supported providers, I want to be able to verify if it's a new user or not (e-mail not in database), without make an additional call from client. Is this possible?
As far as I know, we couldn't directly verify if it's a new user or not.
No matter you use server flow or client flow, easy auth will just return access token for the client to access the mobile backend resources, it will not check the user is new or old.
If you want to achieve this requirement, you need write your own logic.
You could write codes after the user login successfully.
For example, facebook login.
If you the use have login successfully,you could call GetAppServiceIdentityAsync extension method to get the login credentials, which include the access token needed to make requests against the Facebook Graph API.
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var fbRequestUrl = "https://graph.facebook.com/me/feed?access_token="
+ credentials.AccessToken;
// Create an HttpClient request.
var client = new System.Net.Http.HttpClient();
// Request the current user info from Facebook.
var resp = await client.GetAsync(fbRequestUrl);
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
Then you could check the database according to the user information.
More details about how to get user information in server side, you could refer to How to: Retrieve authenticated user information.
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/
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.