1
votes

I am writing a simple desktop application that needs to retrieve some basic properties about a user from Microsoft’ directory. Specifically:

  1. I am writing a single tenant native LOB application.
  2. The application runs on my desktop.
  3. The application runs as my logged on domain account.
  4. The organization' domain accounts are synced to AAD.
  5. 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.
  6. 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:-

  1. 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

  2. When registering the native application in AAD, no key was provided. So I don't have a client secret.

  3. 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

  1. 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.

  2. 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 AAD Application Permissions Setup 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.

3
Have you found the way to solve the exception you got? Because I too get that exact exception Authorization_RequestDenied - user6874415
@Arunkumar Yes. Please see the answer I added on Jan 21. Check with your tenant administrators for AAD. - VJK

3 Answers

1
votes

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.

0
votes

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.

0
votes

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/