8

I have a web application where user logins using the approach defined in this sample.

Now I want to call Microsoft Graph for this User. I have gone through many documents and it's very confusing how this should be done. This is what I have tried. I am not sure how to get the access token for this user.

//not sure about this
var token = await GetAccessToken();

var client = new GraphServiceClient(
    new DelegateAuthenticationProvider(
        requestMessage =>
        {
            requestMessage.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", token);

            return Task.FromResult(0);
        }));

var result = await client
    .Me
    .Request()
    .GetAsync();

As per this documentation, I need to use the Confidential Client Flow, but I am not sure if I need to use the Authorization Code flow or On-Behalf. I don't have access to the Authorization Code because of the approach I followed here.

ConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .WithCertificate(clientCertificate)
    .Build();

Can someone please guide me on how to get the access token for the User? Should I be using Authorization Code flow or On-Behalf?

Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
user911
  • 1,509
  • 6
  • 26
  • 52

3 Answers3

4

If you are following the sample listed above, you are on the right track. Following the tutorial, it shows to how call Microsoft Graph /me endpoint on behalf of the signed-in user. In this sample, the complexities of the ASP.NET Core middleware and MSAL.Net are encapsulated in the Microsoft.Identity.Web section of the tutorial.

You should already have a Web App registered in the Azure Portal. Now that you'll be calling Microsoft Graph, you'll need to register a certificate or secret for the Web App. Then in API permissions, ensure the Microsoft APIs tab is selected and choose the ones you want for Microsoft Graph.

Then, continue following the tutorial to enable MSAL to hook-up to the OpenID Connect events and redeem the authorization code obtained by the ASP.NET Core middleware. Once a token is received, MSAL will save it into a token cache (there is tutorial for this as well).

Keep following the tutorial and you'll add the GraphServiceClientFactory.cs which returns a GraphServiceClient. When it receives an access token for Microsoft Graph, it will make requests to Graph sending the access token in the header. The code is here:

public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
      string accessToken = await acquireAccessToken.Invoke();

      // Append the access token to the request.
      request.Headers.Authorization = new AuthenticationHeaderValue(
      Infrastructure.Constants.BearerAuthorizationScheme, 
      accessToken);
}

There is a bit more set-up to do, but follow the tutorial and you should be able to acquire a token and then use it to call Microsoft Graph.

Jenny
  • 1,211
  • 6
  • 11
2

Note: you will need to reference your project with Microsoft.Graph

First, you will need a function to request for an access token

async Task<string> Post(string uri, Dictionary<string, string> parameters)
{
    HttpResponseMessage response = null;
    try
    {
        using (var httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(30) })
        {
            response = await httpClient.PostAsync(uri, new FormUrlEncodedContent(parameters));
            if (!response.IsSuccessStatusCode)
                throw new Exception("post request failed.");

            var content = response.Content.ReadAsStringAsync().Result;
            if (string.IsNullOrWhiteSpace(content))
                throw new Exception("empty response received.");

            return content;
        }
    }
    catch (Exception e)
    {
        throw new Exception(error);
    }
}

Then you will need a model to handle the response from the web request

public class TokenRequest
{
    [JsonProperty("token_type")]
    public string TokenType { get; set; }

    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
}

Then you will need a function to extract the data from the web request

TokenRequest GetAccessToken()
{
    // request for access token.
    var parameters = new Dictionary<string, string>();
    parameters.Add("client_id", "YOUR CLIENT ID");
    parameters.Add("client_secret", "YOUR CLIENT SECRET");
    parameters.Add("scope", "https://graph.microsoft.com/.default");
    parameters.Add("grant_type", "client_credentials");

    var response = Post(
        $"https://login.microsoftonline.com/{YOUR TENANT ID}/oauth2/v2.0/token",
        parameters).Result;

    return JsonConvert.DeserializeObject<TokenRequest>(response);
}

Then request for an authenticated graph api client

GraphServiceClient GetClient()
{
    var tokenRequest = GetAccessToken();
    var graphClient = new GraphServiceClient(
        new DelegateAuthenticationProvider(
            async (requestMessage) => {
                await Task.Run(() => {
                    requestMessage.Headers.Authorization = new AuthenticationHeaderValue(
                        tokenRequest.TokenType,
                        tokenRequest.AccessToken);

                    requestMessage.Headers.Add("Prefer", "outlook.timezone=\"" + TimeZoneInfo.Local.Id + "\"");
                });
            }));

    return graphClient;
}

Then using the client, you can now perform your queries

var graphClient = GetClient();
var user = await graphClient
    .Users["SOME EMAIL ADDRESS HERE"]
    .Request()
    .GetAsync();

Very important: you also have to make sure you have the proper api permissions on your active directory app registration. Without it, you will only get a request denied response from the graph api.

Hope this helps.

Jeff
  • 760
  • 1
  • 12
  • 26
  • 1
    Jeff, please rather use the libraries rather than coding against the protocol for an ASP.NET / ASP.NET Core application. There are a lot of things to consider which make the direct to protocol approach not secure. Also libraries like MSAL will cache the tokens, keep refresh tokens etc ... – Jean-Marc Prieur Feb 12 '20 at 03:01
  • Hi Jeff, I have WebAPI project, I added your code and Access_Token it generates is not working, if I replace it with the one I generated in Postman it works. Postman and your code uses the same client_id, client_secret and tenantId. Any ideas why that might be the case? Thank you! – Ihor Bats Nov 19 '20 at 19:27
0

You don't actually need an access token to run the graph commands in C#, if you want to run them as the logged in user. In your startup.cs file (or program.cs) you should have the following lines:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
            .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
            .AddInMemoryTokenCaches();

Then, in your application registration, go to the API permissions, as others have said, and set up delegated permissions for Microsoft Graph, for each item you want to access. The delegated permissions represent the permissions of the logged in user in SharePoint, for example. This means that if you run a command to select documents in a library, that user needs to have access to that site and library, in order for you to return any documents in your code.

From there, you should literally be able to run the following code:

GraphServiceClient _graphServiceClient = new GraphServiceClient();
var currentUser = await _graphServiceClient.Me.Request().GetAsync();

I built a GraphHelper class and passed in the GraphServiceClient in the constructor method. The controller that calls the helper class also takes in the GraphServiceClient, and then just passes it directly to the helper. In my code, I never actually "new up" the client, but it works just fine.

One note about this approach. When you do this, your app roles and users all need to be defined in Azure AD. You will not be able to use the Identity Management method where authentication happens with Azure, but authorization happens in your local DB, where you have tables like AspNetRoles, AspNetUsers, etc.. I ran into this issue, which caused me to go another direction. But if you are okay managing everything in Azure, you should be fine.

Andrew Casey
  • 91
  • 1
  • 14