9

I am working with Microsoft Graph and have created an app that reads mail from a specific user.

However, after getting an access token and trying to read the mailfolders, I receive a 401 Unauthorized answer. The detail message is:

The token contains no permissions, or permissions cannot be understood.

This seems a pretty clear message, but unfortunately I am unable to find a solution. This is what I have done so far:

The permissions are: enter image description here enter image description here - Written the code below to acquire an access token:

 // client_secret retrieved from secure storage (e.g. Key Vault)
 string tenant_id = "xxxx.onmicrosoft.com";
 ConfidentialClientApplication client = new ConfidentialClientApplication(
 "..",
 $"https://login.microsoftonline.com/{tenant_id}/",
 "https://dummy.example.com", // Not used, can be any valid URI 
 new ClientCredential(".."),
 null, // Not used for pure client credentials
 new TokenCache());
   string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
   AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result;
   string token = result.AccessToken;

So far so good. I do get a token.

Now I want to read the mail folders:

url = "https://graph.microsoft.com/v1.0/users/{username}/mailFolders";
handler = (HttpWebRequest)WebRequest.Create(url);
handler.Method = "GET";
handler.ContentType = "application/json";
handler.Headers.Add("Authorization", "Bearer " + token);
response = (HttpWebResponse)handler.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
    returnValue = sr.ReadToEnd();
}

This time I receive a 401 message, with the details:

The token contains no permissions, or permissions cannot be understood.

I have searched the internet, but can’t find an answer to why my token has no permissions.

Thanks for your time!

update 1

If I use Graph Explorer to read the mailfolders, then it works fine. Furthermore: if I grap the token id from my browser en use it in my second piece of code, then I get a result as well. So, the problem is really the token I receive from the first step.

Roeland
  • 820
  • 1
  • 9
  • 33
  • You can paste your token into https://jwt.io. There you can check if the payload really lists the needed permissions. Otherwise re-check which permissions you gave and maybe re-run the admin consent for your application. – Oliver Jan 17 '19 at 13:05
  • Hi Oliver, thanks for your reply. I have copy pasted in the validator. The decoded response seems OK, but honoustly I don't know where to look for the permissions. – Roeland Jan 18 '19 at 19:36
  • If you take a look at the decoded payload, there are some commonly known jwt properties like `iat`, `nbf`, `exp`. And a little bit lower, there is a property `scp`. This contains all the scopes that you can access. This is a space separated list that should contain the text *Mail.Read* or *Mail.ReadWrite*. If that's not the case, you don't have the permission. – Oliver Jan 21 '19 at 06:52
  • Than this is probably the issue: I don't have a scp property at all. – Roeland Jan 21 '19 at 12:48
  • Then is something wrong on how you ask for the graph.microsoft.com token. Don't know what, but I request [the application token differently then you](https://stackoverflow.com/a/53500109/1838048). – Oliver Jan 21 '19 at 13:49

1 Answers1

5

To ensure this works like you expect, you should explicitly state for which tenant you wish to obtain the access token. (In this tenant, the application should, of course, have already obtained admin consent.)

Instead of the "common" token endpoint, use a tenant-specific endpoint:

string url = "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token";

(Where {tenant-id} is either the tenant ID of the tenant (a Guid), or any verified domain name.)

I would also strongly recommend against building the token request on your own, as you show in your question. This may be useful for educational purposes, but will tend to be insecure and error-prone in the long run.

There are various libraries you can use for this instead. Below, an example using the Microsoft Authentication Library (MSAL) for .NET:

// client_secret retrieved from secure storage (e.g. Key Vault)
string tenant_id = "contoso.onmicrosoft.com";
ConfidentialClientApplication client = new ConfidentialClientApplication(
    client_id,
    $"https://login.microsoftonline.com/{tenant_id}/",
    "https://dummy.example.com", // Not used, can be any valid URI 
    new ClientCredential(client_secret),
    null, // Not used for pure client credentials
    new TokenCache());

string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = client.AcquireTokenForClientAsync(scopes).Result
string token = result.AccessToken;
// ... use token
Philippe Signoret
  • 13,299
  • 1
  • 40
  • 58
  • Hi Philippe, thank you for your answer. I did implement this code, but at requesting the mail API with the token I still receive the same error response. I am sure there must be something wrong with registring the app. I upvoted your answer because I appreciate the library suggestion and the effort! – Roeland Jan 18 '19 at 19:40
  • Are you completely sure the permission was granted? You can confirm under Azure portal > Enterprise apps > (find your app) > Permissions. Also, can you please update your question so that it matches what your latest version of the code is? – Philippe Signoret Jan 18 '19 at 22:57
  • I just added the permissions in the question. These I can see on apps.dev.microsoft.com (first image) and on portal.azure.com (second image) – Roeland Jan 20 '19 at 18:33
  • 1
    You still seem to be using the `common` endpoint to acquire the token (`https://login.microsoftonline.com/common/`), instead of the tenant-specific endpoint (`https://login.microsoftonline.com/{tenant-id}`). Did you try with the tenant-specific endpoint? (Replace the line `$"https://login.microsoftonline.com/common/",` with `$"https://login.microsoftonline.com/{tenant_id}/",`, like in my example.) – Philippe Signoret Jan 20 '19 at 20:10
  • Sorry, you are right. I did change the tenant-id, but forgot to use this variable in de url. The result however is the same. – Roeland Jan 21 '19 at 12:47