How to call Microsoft Graph in a dotnet core app with multiple scopes and auth types

3.6k views Asked by At

I'm attempting to build an application that can read from Microsoft Graph both as the user and as the application. For example, I need to call https://graph.microsoft.com/beta/me/calendar/events as the logged in user to get their events for the day. However, I also need the application itself to fetch all of the events for all of the conference rooms using https://graph.microsoft.com/beta/users/[email protected]/events.

I have successfully created the application in Azure AD and provided the appropriate Graph permissions to both delegates and applications as well as approved them for my tenant.

enter image description here

I have also used Microsoft.Identity builder configuration in Program.cs to successfully connect to my tenant and fetch my personal Graph information.

However, what I'd like to do now is be able to inject two graph connections. One for the application and one for the user. That way, when my application needs to fetch data as the application, I can call one Graph connection, and when it needs to fetch data as the user, I can call the other Graph connection.

Is it possible to configure and inject two Graph connectors, one as the user and the other as the application, in the same application?

2

There are 2 answers

0
Randy Slavey On BEST ANSWER

After numerous attempts at varying ways to inject two graph clients, it turns out there's a built-in method for it.

        var me = await graphServiceClient.Me.Request().GetAsync();
        var otherUser = await graphServiceClient.Users["[email protected]"].Request().WithAppOnly().GetAsync();

The key method there is "WithAppOnly()", which says to fetch from Graph using the app permissions not the delegated user permissions. The only scopes I had to add were "profile user.read.all", with "profile" being delegated and "user.read.all" being an application permissions:

enter image description here

Edit: Found solution here: How to dependency inject Microsoft Graph client in ASP.Net Core 6 Web Api

1
Tiny Wang On

It's possible I think.

Firstly, when we need the web app to have the ability to call graph api on behalf of a user(using https://graph.microsoft.com/beta/me), we need to integrate microsoft authentication module so that uses can sign in first then app can know who is me.

Then in asp.net core application, microsoft provide graph sdk for helping calling ms graph api.

Now we need to integrate ms sign in module and graph sdk into your asp.net core app, you can refer to this sample or my code(based on asp.net core MVC 5) below.

Adding login in partial view page named _LoginPartial.cshtml and add it into _layout.cshtml:

@using System.Security.Principal

<ul class="navbar-nav">
@if (User.Identity.IsAuthenticated)
{
        <li class="nav-item">
            <span class="navbar-text text-dark">Hello @User.Identity.Name!</span>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a>
        </li>
}
else
{
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
        </li>
}
</ul>

enter image description here

Then add configurations in appsettings.json:

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "your_tenant_name.onmicrosoft.com",
    "TenantId": "tenant_id",
    "ClientId": "azure_ad_app_client_id", 
    "ClientSecret": "client_secret", 
    "CallbackPath": "/signin-oidc",//you need to add redirect url in azure portal->azure ad->your app->authentication->web platform->add redirect url like https://localhost:44321/signin-oidc
    "SignedOutCallbackPath ": "/signout-callback-oidc"
  }

Then modify Startup.cs, don't forget adding app.UseAuthentication();

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Authorization;
public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "user.read" })
        .AddMicrosoftGraph(options =>
        {
            options.Scopes = string.Join(' ', new string[] { "user.read" });
        })
        .AddInMemoryTokenCaches();

    // Require authentication
    services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    })
    // Add the Microsoft Identity UI pages for signin/out
    .AddMicrosoftIdentityUI();
}

Then in the controller, update it like this, you can call graph api with /me endpoint.

using Microsoft.AspNetCore.Authorization;
using Microsoft.Graph;
using Azure.Identity;

[Authorize]
public class HomeController : Controller
{
    private readonly GraphServiceClient _graphClient;
    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> IndexAsync()
    {
        var me = await _graphClient.Me.Request().GetAsync();
        ViewBag.Myname = me.DisplayName;
        return View();
    }
}

Then when you want the app to call graph api on behalf of the application itself, we need to use client credential flow. So here's my sample, just modify the controller method:

public async Task<IActionResult> IndexAsync()
{
    var me = await _graphClient.Me.Request().GetAsync();
    ViewBag.Myname = me.DisplayName;

    var scopes = new[] { "https://graph.microsoft.com/.default" };
    var tenantId = "your_tenant_name.onmicrosoft.com";
    var clientId = "azure_ad_client_id";
    var clientSecret = "client_secret";
    var clientSecretCredential = new ClientSecretCredential(
        tenantId, clientId, clientSecret);
    var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
    var user = await graphClient.Users["tinytest@your_tenant_name.onmicrosoft.com"].Request().GetAsync();
    ViewBag.Username = user.DisplayName;
    return View();
}

The nuget packages I installed:

<PackageReference Include="Microsoft.Graph" Version="4.19.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="1.24.1" />
    <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.24.1" />
    <PackageReference Include="Microsoft.Identity.Web.UI" Version="1.24.1" />

Test result:

enter image description here