Calling Microsoft Graph API from inside Azure Functions

Viewed 7537

I'm trying to write a simple Azure Function that calls the Microsoft Graph API. But I could not make the access_token work. Here is what I've done:

  1. Created a new Azure Function App from the Azure Portal
  2. Turned on the "App Service Authentication" setting and instructed it to sign in with AAD (management mode is Express).
  3. Configured the app to have delegated permissions like "Sign in and read user profile" for Microsoft Graph.
  4. Created a new JavaScript function HttpTriggerJS1
  5. Changed the authorization level of this function to "Anonymous" (otherwise by default the "Function" level would not even allow me to run the function, always returning 401 Unauthorized)
  6. Installed the necessary Node module (npm install request)
  7. And the actual function:

    var request = require('request');
    module.exports = function (context, req) {
        var token = req.headers['x-ms-token-aad-access-token'];
        var reqUrl = 'https://graph.microsoft.com/v1.0/me/';
        request.get(reqUrl, {'auth': {'bearer': token}}, function (err, response, msg) {
            context.res = {
                body: msg
            };
            context.done();
        });
    };
    
  8. Tested this function in a separate browser window. Signed me in to AAD correctly.

  9. But the message returned from Graph was:

    "{
      "error": {
        "code": "InvalidAuthenticationToken",
        "message": "CompactToken parsing failed with error code: -2147184105",
        "innerError": {
          "request-id": "4c78551d-f0fe-4104-b1d3-e2d96fd3c02c",
          "date": "2017-05-16T19:11:14"
        }
      }
    }"
    

I looked into the token I got from req.headers['x-ms-token-aad-access-token']. It's something like "AQABAA....", which seems different from the regular access_token I've seen before that starts with "eyJ....".

What could be wrong here? When calling the Graph API, am I supposed to be using the access_token from the request headers?

Thanks!

Edit:

According to Chris Gillum's suggestion, I also looked into the "on-behalf-of" flow. And here is my updated function, which acquires an access_token for a particular resource (https://graph.microsoft.com in my case) by providing the id_token (retrieved from the request headers).:

var request = require('request');

module.exports = function (context, req) {
    var parameters = {
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        client_id: process.env.WEBSITE_AUTH_CLIENT_ID,
        client_secret: process.env.WEBSITE_AUTH_CLIENT_SECRET,
        assertion: req.headers['x-ms-token-aad-id-token'],
        resource: 'https://graph.microsoft.com',
        requested_token_use: 'on_behalf_of'
    };
    request.post('https://login.microsoftonline.com/microsoft.com/oauth2/token', {form: parameters}, function (aadErr, aadResponse, aadMsg) {
        var msgJson = JSON.parse(aadMsg);
        request.get('https://graph.microsoft.com/v1.0/me/', {'auth': {'bearer': msgJson.access_token}}, function (err, response, msg) {
            context.res = {
                body: msg
            };
            context.done();
        });
    });
};
3 Answers

Azure Functions now supports native authentication for the Microsoft Graph. Docs are at https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-microsoft-graph

There's also a video at https://azure.microsoft.com/en-us/resources/videos/azure-friday-navigating-the-microsoft-graph-with-azure-functions-henderson/

For example, you can create an HttpTrigger function and add the following to function.json.

{
   "type": "token",
   "direction": "in",
   "name": "graphToken",
   "resource": "https://graph.microsoft.com",
   "identity": "userFromRequest"
}

Then, you can query the Graph API on behalf of the user making the request. The access token is passed in as a parameter that you can add as a header to an HttpClient

using System.Net; 
using System.Net.Http; 
using System.Net.Http.Headers; 

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, string graphToken, TraceWriter log)
{
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", graphToken);
    return await client.GetAsync("https://graph.microsoft.com/v1.0/me/");
}

You can also run functions with the ClientCredentials authentication mode, which means it runs as an app instead of in the context of a particular user.

Related