Ortund
Ortund

Reputation: 8255

Why doesn't my Azure Active Directory auth token contain permissions for MS Graph?

I'm working in Postman before I start writing app code so that I can get my head around what I'm actually supposed to be doing - it's educational; in actual app dev, I'll be using MSAL for auth.

In the meantime, I can't even manage to get a token that works.

My app on Azure has the Calendar.Read and Calendar.ReadWrite permissions set in the API Permissions blade of the Azure Portal.

I've created a client secret for the app (so using client credentials flow) and I'm providing the /.default scope per this accepted answer on MS docs.

When I try to query MS graph at https://graph.microsoft.com/v1.0/users/<my user id>/events I get the error saying:

The token contains no permissions, or permissions can not be understood

Sure enough, the token contains neither the scp or roles claims.

I've also referenced csharpcorner to get a token as well as the actual documentation to configure the request in Postman.

I don't understand where I'm going wrong. As far as I can tell I'm doing everything the way I'm supposed to be but I'm just not getting anywhere.

Where am I going wrong? Have I missed something? Is there something I'm doing wrong?

Here's my jquery code from Postman:

var settings = {
  "url": "https://login.microsoftonline.com/redacted/oauth2/token",
  "method": "POST",
  "timeout": 0,
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  "data": {
    "grant_type": "client_credentials",
    "client_id": "redacted",
    "client_secret": "redacted",
    "resource": "https://graph.microsoft.com",
    "scope": "./default"
  }
};

$.ajax(settings).done(function (response) {
  console.log(response);
});

Your help is greatly appreciated.

Thanks in advance

Upvotes: 2

Views: 1053

Answers (3)

Carl Zhao
Carl Zhao

Reputation: 9549

Okay, that's the problem, you have not granted Calendars.Read or Calendars.ReadWrite application permissions.

Because you are using the client credential flow to get the token and call the /users/{user id} endpoint to list the events of other users. So you must grant application permissions and grant admin consent for that permission. Then your problem will be solved.

enter image description here

Upvotes: 3

Tiny Wang
Tiny Wang

Reputation: 16066

Which language are you using? In my opinion, you can't use jquery to send an ajax request to obtain an access token, that should be a CORS error.

In the meanwhile, client credential flow should be used in daemon applications, but the code you provides are likely to be a single-page application. Here's a document contains many samples for different kind of applications and you may choose one to generate your access token.

enter image description here

Here's my another answer on obtaining access token for calling key vault, you may refer to it. If I misunderstood in some place, pls point it out. You can also provide more details on your further issue.

=========================================================

frontend code, obtain access token from backend and calling graph api:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    </head>
    <body>
        <div>
            <div id="subject"></div>
            <div>create time: <span id="cDateTime"></span></div>
            
        </div>
        <script src="../js/jquery-3.5.1.min.js"></script>
        <script type="text/javascript">
            $(function() {
                initPage();
            });
            
            function initPage(){
                $.ajax({
                    url: "https://localhost:44343/",
                    type: 'get',
                    contentType: "application/json;charset=utf-8",
                    success: function(data) {
                        callApi(data);
                    },
                    error: function(data) {
                        console.info(data);
                    }
                })
            }
            
            function callApi(accesstoken){
                $.ajax({
                    url: "https://graph.microsoft.com/v1.0/users/{user_id_here}/calendar/events",
                    type: 'get',
                    headers: {
                        Authorization: "Bearer " + accesstoken
                    },
                    dataType: 'json',
                    success: function(data) {
                        var createdDateTime = data.value[0].createdDateTime;
                        var subject = data.value[0].subject;
                        $("#subject").html(subject);
                        $("#cDateTime").html(createdDateTime);
                    },
                    error: function(data) {
                        console.info(data);
                    }
                })
            }
        </script>
    </body>
</html>

backend code(home controller), using client credential flow to generate access token, and send it to front end. Don't forget to deal with cores error if you choose to use frontend-backend-separate.

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Client;
using System;
using System.Threading.Tasks;


namespace crendentailflow_web_mvc.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public async Task<string> Index()
        {
            //I saved the client secret in azure key vault, and you can aslo use it directly
            const string secretName = "clientsecret";
            var kvUri = "https://vaultname.vault.azure.net/";
            var a = new DefaultAzureCredential();
            var client = new SecretClient(new Uri(kvUri), a);
            var secret = await client.GetSecretAsync(secretName);
            string secretVaule = secret.Value.Value;

            IConfidentialClientApplication app;
            app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_client_id")
                    .WithClientSecret(secretVaule)
                    .WithAuthority(new Uri("https://login.microsoftonline.com/your_tenant_name.onmicrosoft.com"))
                    .Build();

            AuthenticationResult result = null;
            // don't forget to add api permission in azure portal
            string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
            result = await app.AcquireTokenForClient(scopes)
                    .ExecuteAsync();
            string accesstoken = result.AccessToken;
            return accesstoken;
        }
    }
}

dependencies:

<PackageReference Include="Azure.Identity" Version="1.4.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.1.0" />

Upvotes: 0

Thiago Custodio
Thiago Custodio

Reputation: 18387

Take a look if you're using implicit flow otherwise, there's a max length issue and the content of the token will be truncated.

https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/239

Upvotes: 1

Related Questions