2

I am trying to get information about users from Microsoft Graph via https://graph.microsoft.com/v1.0/users.

It's returning a 401 - Unauthorized:

{
  "error": {
    "code": "InvalidAuthenticationToken",
    "message": "Access token validation failure. Invalid audience.",
    "innerError": {
      "request-id": "3157d513-6f31-4d2d-a3d7-a97eed7207ba",
      "date": "2019-12-11T05:39:02"
    }
  }
}

My code:

AuthenticationContext authContext =
    new AuthenticationContext(string.Format(CultureInfo.InvariantCulture,
        "https://login.microsoftonline.com/{0}", "my-domain name"));

ClientCredential clientCred =
    new ClientCredential("Client-id", "Client-Secret-id");

AuthenticationResult authenticationResult = authContext
    .AcquireTokenAsync("https://graph.windows.net", clientCred).Result;

var token = authenticationResult.AccessToken;

var client = new HttpClient();
var uri = "https://graph.microsoft.com/v1.0/me/";

client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(token);
var response = await client.GetAsync(uri);

Where I did go wrong? Why I am not getting a proper access token? Could anyone please help me to use the MS Graph?

Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
Md Aslam
  • 1,228
  • 8
  • 27
  • 61

5 Answers5

1

You use the wrong resource, you need to get the token for Microsoft Graph instead of AAD Graph, it should be https://graph.microsoft.com, not https://graph.windows.net.

AuthenticationResult authenticationResult = authContext.AcquireTokenAsync("https://graph.microsoft.com",
                      clientCred).Result;

Update:

Make sure you grant the User.Read.All Application permission.

enter image description here enter image description here enter image description here enter image description here

Then try the code as below, it works on my side.

using System;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            string _authString = "https://login.microsoftonline.com/xxxxxx.onmicrosoft.com";
            string _clientId = "<client-id>";
            string _clientSecret = "<client-secret>";
            AuthenticationContext authenticationContext = new AuthenticationContext(_authString, false);
            ClientCredential clientCred = new ClientCredential(_clientId, _clientSecret);
            AuthenticationResult authenticationResult;
            authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCred).GetAwaiter().GetResult();
            Console.WriteLine(authenticationResult.AccessToken);

            var token = authenticationResult.AccessToken;

            var client = new HttpClient();
            var uri = "https://graph.microsoft.com/v1.0/users";

            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
            client.DefaultRequestHeaders.Accept.Clear();
            //GET Method  
            HttpResponseMessage response = client.GetAsync(uri).GetAwaiter().GetResult();
            Console.WriteLine(response.Content.ReadAsStringAsync().Result.ToString());
        }
    }
}

enter image description here

Joy Wang
  • 39,905
  • 3
  • 30
  • 54
  • I have changed and tried but it gives Not found exception: { "error": { "code": "Request_ResourceNotFound", "message": "Resource 'dee6a1f4-6a0e-43fb-93dc-4d9851cf2920' does not exist or one of its queried reference-property objects are not present.", "innerError": { "request-id": "5fec2923-583c-44ec-b25a-236795be3a0e", "date": "2019-12-11T06:14:16" } } } Do you have any Idea? – Md Aslam Dec 11 '19 at 06:15
  • Thank you so much for your response and update, I have tried your code but It shows Request_ResourceNotFound for the request ".../me " and Authorization_RequestDenied for the URl ".../users". If I copy-paste the token from Graph explorer directly then it is working fine, so I hope the token is not proper here, Do you have any idea? Meanwhile, I will change the permission for user.all.read and try . – Md Aslam Dec 11 '19 at 07:25
  • 1
    @MdAslam 1.Please don't use `/me` in your code, if you want to get a specific user, use `https://graph.microsoft.com/v1.0/users/{object-id or userPrincipalName}`, `/me` will work in explorer, because you logged in it. 2.For the `Authorization_RequestDenied` with `/users`, you should grant the `User.Read.All` application permission for your AD App and **don't forget to click the `Grant admin consent button`**. If you are not sure, provide your api permission page of your app in the question, I can take a look at it. – Joy Wang Dec 11 '19 at 07:32
  • @MdAslam Updated the screenshot of adding the permission, you can take a look. – Joy Wang Dec 11 '19 at 07:41
  • @MdAslam Hi, could you get it now? – Joy Wang Dec 11 '19 at 09:24
  • 1
    Hi Joy, I have requested my devops team to grand the permission so once they give I will try and come back. – Md Aslam Dec 12 '19 at 05:48
  • I have one small clarification that, I am able to invoke the GET API methods by using Graph API Explorer but it's not working from .Net Core code. I think the token is not a valid one, i.e It is an Id token but not an access token, So to connect graph api, the token should contain the user details right? I have validated the token in online but payload not contains my user details so no need to worry about that? – Md Aslam Dec 12 '19 at 05:50
  • 1
    @MdAslam It depends on which flow you are using, if you are using the [client credential flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow), the permissions are just related to the App, not the user. In Graph explorer, it should use [implicit grant flow](https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-implicit-grant-flow). In this case, your code is client credential flow. – Joy Wang Dec 12 '19 at 05:57
  • Hi joy, Can you give me a sample code or document links for the User Credential Flow? Because I am not able to grant consent to the permissions, please check my query to understand the issue in client credential flow. Still, I am waiting to enable the Read.All permission, so meanwhile I will try the user flow. https://stackoverflow.com/questions/59356561/azure-grant-admin-consent-for-arcadis-not-editable. – Md Aslam Dec 17 '19 at 09:46
  • @MdAslam You could follow this [link](https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#usernamepassword-provider) to create the `Username/password provider`, then use the [MS graph sdk](https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=csharp#request), but I don't recommend you to use that, if your account is MFA-enabled, you will not be able to use it. – Joy Wang Dec 17 '19 at 09:51
  • @MdAslam Also,note you need to give the Delegated permission `User.Read.All`, see this [link](https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=csharp#permissions). – Joy Wang Dec 17 '19 at 09:53
  • Thank you so much for your time and response. I hope you might have checked my question/problem in the mentioned Stackoverflow link (previous comment), My team/ admin members also not having the admin user access to grant permission in azure portal, so I am waiting for that only. Once get the User.Read.All access, I will update. – Md Aslam Dec 17 '19 at 12:28
  • @MdAslam You need the permissions, otherwise, you can't do that. – Joy Wang Dec 18 '19 at 01:18
  • Thank you so much. I got the application-level access and working fine. – Md Aslam Feb 25 '20 at 12:41
1

I think if you're calling Microsoft Graph the resource needs to be https://graph.microsoft.com instead of AAD Graph (graph.windows.net). Can you try changing that in your AcquireTokenAsync call?

David
  • 2,412
  • 1
  • 14
  • 22
  • I have changed and tried but it gives Not found exception: { "error": { "code": "Request_ResourceNotFound", "message": "Resource 'dee6a1f4-6a0e-43fb-93dc-4d9851cf2920' does not exist or one of its queried reference-property objects are not present.", "innerError": { "request-id": "5fec2923-583c-44ec-b25a-236795be3a0e", "date": "2019-12-11T06:14:16" } } } Whats the problem here? – Md Aslam Dec 11 '19 at 06:16
1

There are two issues :

  1. Wrong resource , the resource should be https://graph.microsoft.com . And confirm that you have grant correct Microsoft Graph's permissions in Azure AD portal

  2. You are using client credential flow as using AcquireTokenAsync(String, ClientCredential) method without user , so https://graph.microsoft.com/v1.0/me/ won't work since there is no user in it . Use GET /users/{id | userPrincipalName} instead . Also , you should grant Application Permission in azure portal since you are using M2M flow .

    Permissions (from least to most privileged) :

    Application :User.Read.All, User.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All

Nan Yu
  • 26,101
  • 9
  • 68
  • 148
1

Make sure the account you're using while making the Graph API calls has the Required Permissions. As you're invoking a GET call,

Below permissions should be set up.

Graph Api Read Permission

More about permissions here: https://learn.microsoft.com/en-us/graph/permissions-reference

The Error posted clearly states that the account you're using to make calls to GRAPH API is unauthorized. Have the permissions set right and the access token will be generated and will be authenticated against your application.

EDIT: Try the below code to get a valid access token.


        static string AppID = "<Your Application ID>";
        static string APPKey = "<Your Application Key>";        
        static string tenantId = "<Your ORG Tenant ID>";
        static string RedirectURI = "<Your Application's custom Redirect URI>";
        static string GraphApi = "https://graph.microsoft.com/v1.0/"


public static IAuthenticationProvider CreateAuthorizationProvider()
        {            
            var authority = $"https://login.microsoftonline.com/{tenantId}/v2.0";

            List<string> scopes = new List<string>();
            scopes.Add("https://graph.microsoft.com/.default");

            var cca = ConfidentialClientApplicationBuilder.Create(AppID)
                                                    .WithAuthority(authority)
                                                    .WithRedirectUri(RedirectURI)
                                                    .WithClientSecret(APPKey)
                                                    .Build();
            return new MsalAuthenticationProvider(cca, scopes.ToArray());
        }
        public static HttpClient GetAuthenticatedHTTPClient()
        {
            var authenticationProvider = CreateAuthorizationProvider();
            _httpClient = new HttpClient(new AuthHandler(authenticationProvider, new HttpClientHandler()));
            return _httpClient;
        }

private static async Task<User> GetADUserInfo(HttpClient client,string email)
        {
            User user = new User();           
            client = GetAuthenticatedHTTPClient();
            client.BaseAddress = new Uri(GraphApi);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            WriteToConsole("Call Graph API :: retrieving AD Info for the employee ::" + email);
            using (client)
            {
                try
                {
                    HttpResponseMessage res = await client.GetAsync("users/" + email);
                    res.EnsureSuccessStatusCode();
                    if (res.IsSuccessStatusCode)
                    {                        
                        user = await res.Content.ReadAsAsync<User>();
                        WriteToConsole("Call Graph API :: Call Success for employee ::" + email);

                    }                    
                }                
                catch (Exception ex)
                {

                    LogError(ex, "Error in Getting AD User info via Graph API");
                    return null;
                }
                return user;
            }
        }

The Above code uses MSALAuthentication, Use the code below :

public class MsalAuthenticationProvider : IAuthenticationProvider
    {
        private IConfidentialClientApplication _clientApplication;
        private string[] _scopes;

        public MsalAuthenticationProvider(IConfidentialClientApplication clientApplication, string[] scopes)
        {
            _clientApplication = clientApplication;
            _scopes = scopes;
        }

        public async Task AuthenticateRequestAsync(HttpRequestMessage request)
        {
            var token = await GetTokenAsync();
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
        }

        public async Task<string> GetTokenAsync()
        {
            AuthenticationResult authResult = null;
            authResult = await _clientApplication.AcquireTokenForClient(_scopes).ExecuteAsync();
            return authResult.AccessToken;
        }
    }

AuthHandler Class :

public class AuthHandler : DelegatingHandler
        {
            private IAuthenticationProvider _authenticationProvider;

            public AuthHandler(IAuthenticationProvider authenticationProvider, HttpMessageHandler innerHandler)
            {
                InnerHandler = innerHandler;
                _authenticationProvider = authenticationProvider;
            }

            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                await _authenticationProvider.AuthenticateRequestAsync(request);
                return await base.SendAsync(request, cancellationToken);
            }
        }
Mathew
  • 21
  • 4
  • I have permissions and I am able to invoke the GET API methods by using Graph API Explorer but it's not working from .Net Core code. I think the token is not a valid one, i.e It is an Id token but not an access token, So to connect graph api, the token should contain the user details right or not required? – Md Aslam Dec 12 '19 at 05:47
  • The Token shouldn't contain user details. Authorization token is created by Tenant ID, Application ID, Application Key, ReditectURi (Setup on Azure Portal) – Mathew Dec 13 '19 at 21:53
  • I have tried your code but the response shows res.ReasonPhrase is "Forbidden" status code:403 and the Exception message is"Response status code does not indicate success: 403 (Forbidden)." – Md Aslam Dec 17 '19 at 09:42
  • Involve your Infra Team, no issues with the code. It's Permission/Access rights issues. As the code clearly states 403. I just cannot stress this enough. – Mathew Dec 18 '19 at 20:08
0

You have a few issues going on:

  1. You should be requesting a token for https://graph.microsoft.com, not https://graph.windows.net. The graph.windows.net is the older AAD Graph, not the newer Microsoft Graph:

    AuthenticationResult authenticationResult = authContext
      .AcquireTokenAsync("https://graph.windows.net", clientCred).Result;
    
  2. You cannot use /me with the Client Credentials grant. Graph translates /me into /users/{currently authenticated user id}. Since you're not authenticating a user, the "currently authenticated user id" is null:

    var uri = "https://graph.microsoft.com/v1.0/users/user@domain.onmicrosoft.com";
    
  3. You are setting the Authorization header's value but not the scheme. You need to set both:

    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", token);
    
  4. It isn't clear from your question which scopes you've requested or if you've received Admin Consent. You need to make sure you've requested the Application scope User.Read.All and received Admin Consent from a tenant administrator.

Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63