3
votes

I'm trying to call Microsoft Graph using REST API, and I'm having some problems. My app will eventually be a web app deployed to Azure, and I need to call Graph via REST without a logged-in user.

In trying to debug this, I tried to make the simplest app that I could. This app is just trying to read a user's profile from Azure Active Directory using Graph.

I registered my app in AAD, so I have a tenant, client ID and a client secret. At this point, I've given it every permission under the AAD and Graph APIs (for testing purposes). I'm able to get an token from AAD, but when I call the Graph API with this token, I get 401 - Unauthorized, with Access Token Validation Error.

I've searched for this error, and haven't found anything that seems to apply.

EDIT: I've also Granted the permissions to my app after added the permissions.

I've grabbed pieces from various samples in an attempt to get this working. Here's the code:

var tenant = "tenant ID";
var clientID = "app ID";

// I've tried graph.microsoft.com and graph.microsoft.com/.default
var resource = "https://graph.microsoft.com/user.read.all";
var secret = "client secret";

string token;

using(var webClient = new WebClient())
{
    var requestParameters = new NameValueCollection();
    requestParameters.Add("scope", resource);
    requestParameters.Add("client_id", clientID);
    requestParameters.Add("grant_type", "client_credentials");
    requestParameters.Add("client_secret", secret);

    var url = $"https://login.microsoftonline.com/{tenant}/oauth2/token";
    var responseBytes = await webClient.UploadValuesTaskAsync(url, "POST", requestParameters);
    var responseBody = Encoding.UTF8.GetString(responseBytes);

    var jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(responseBody);
    token = jsonObject.Value<string>("access_token");
}
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

var response = await client.GetAsync(new Uri("https://graph.microsoft.com/v1.0/user/<user ID>"));

That second call comes back with the 401 - Unauthorized.

Does anyone see anything that I'm doing wrong, or that I should check?

Thanks!

EDIT: Here's the JSON from the token, decoded with Fiddler with tenant and client IDs removed:

{
    "typ": "JWT",
    "alg": "RS256",
    "x5t": "HHByKU-0DqAqMZh6ZFPd2VWaOtg",
    "kid": "HHByKU-0DqAqMZh6ZFPd2VWaOtg"
} 
{
    "aud": "spn:00000002-0000-0000-c000-000000000000",
    "iss": "https://sts.windows.net/<tenant id>/",
    "iat": 1507313785,
    "nbf": 1507313785,
    "exp": 1507317685,
    "aio": "Y2VgYCguP8H/lUPs5seMpgeOze3vAA==",
    "appid": "~client id~",
    "appidacr": "1",
    "idp": "https://sts.windows.net/~tenant id~/",
    "oid": "37df6326-7ed1-4b88-a57c-b82319a5cb07",
    "sub": "37df6326-7ed1-4b88-a57c-b82319a5cb07",
    "tenant_region_scope": "NA",
    "tid": "~tenant id~",
    "uti": "IspHJJNqjUKWjvsWmXhDAA",
    "ver": "1.0"
}
2
There is a problem with the Audience of your token. "00000002-0000-0000-c000-000000000000" is the App ID for the AAD Graph API. Also you have no "Role" claims, which means you will have no access to call any API anyway...Shawn Tabrizi
Yeah... pretty sure the problem is here: requestParameters.Add("scope", resource). Should be requestParameters.Add("resource", resource) and resource should be https://graph.microsoft.com. See details of the authentication procedure here.Shawn Tabrizi
Thanks, I'll give it a try!Phil Mar
Thanks Shawn - that worked!Phil Mar
Another question, if I may: the docs here show that 'scope' should be passed: developer.microsoft.com/en-us/graph/docs/concepts/… - Should they be corrected?Phil Mar

2 Answers

6
votes

After looking at the code sample and access token you edited in to your question, I can point out a few problems:

  1. The access token you are getting is not valid for calling https://graph.microsoft.com. Note that the aud claim in the token has the app id 00000002-0000-0000-c000-000000000000 which is the app id for the AAD Graph API, which is a similar but different endpoint.
  2. The token you have does not have any role claims, which is what is used for granting access to a client app trying to call in the App Only flow. This is probably a result of the first problem, fixing your resource should also then populate the role claims.

When looking at your code to see where the resource problem is occurring, we can see that you are slightly mixing up the V1 and V2 authentication procedures.

Your code seems to want to follow the V1 procedure, so you need to make sure your POST request to get a token has the following parameters: grant_type, client_id, client_secret, resource.

In your code sample, you seem to have added a scope parameter rather than a resource parameter. This is consistent with the V2 method, where we support dynamic consent, where you can define the scopes you want right when you are getting a token, but this does not work with the V1 endpoint.

Instead, you should update these two lines in your code:

var resource = "https://graph.microsoft.com";

and

requestParameters.Add("resource", resource);

Let me know if this helps.

0
votes

I believe all you need to do here is click "Grant Permissions" in the required permissions menu.

enter image description here

Basically, adding permissions to your App is not good enough. Your application must also have a user consent to give it access to call the Graph API using App Only permissions. The Grant Permissions button is one way to grant consent to your application. The other method would be to simply sign into the application, which will prompt the user to consent.

Let me know if this helps!