Search code examples
c#azureasp.net-core-mvc

Microsoft Graph client in a ASP.NET Core MVC web app error


I am trying to get some graph to get return some user attributes from Entra. I need to use graphClient.Me.GetAsync().

Update: My end result is that I need to use var result = await graphClient.Users["{user-id}"].GetAsync((requestConfiguration) => { requestConfiguration.QueryParameters.Select = new string []{ "displayName","givenName","postalCode","identities" }; }); to get some attributes that graphClient.Me.GetAsync() does not return, and I believe that is only supported with Microsoft.Graph 5.61, but I could be wrong.

When I run my project from Visual Studio I get prompted for my Entra credentials, then I get this error message:

An unhandled exception occurred while processing the request.
TypeLoadException: Could not load type 'Microsoft.Graph.IAuthenticationProviderOption' from assembly 'Microsoft.Graph.Core, Version=3.1.22.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxx'.

Here is my setup - I have added these packages:

Microsoft.Graph v.5.61
Microsoft.Identity.Web 3.2.2 
Microsoft.Identity.Web.MicrosoftGraph v. 3.2.2

Added to appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "yourdomain.onmicrosoft.com",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "ClientSecret": "your-client-secret",
    "CallbackPath": "/signin-oidc"
  }
}

Added to Program.cs:

using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

// Add authentication and Microsoft Identity
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

// Add Graph API client
builder.Services.AddMicrosoftGraph(options =>
{
    options.Scopes = "User.Read"; // Ensure this scope is granted in Azure portal
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

And this is my controller:

[Authorize]
public class HomeController : Controller
{
    private readonly GraphServiceClient _graphClient;

    public HomeController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<IActionResult> Index()
    {
        // Get the user's information
        var user = await _graphClient.Me.GetAsync();

        // Access the user's email
        var email = user.Mail ?? user.UserPrincipalName;

        ViewBag.Email = email;

        return View();
    }
}

Here is the csproj file

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Graph" Version="5.61.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="3.2.2" />
    <PackageReference Include="Microsoft.Identity.Web.GraphServiceClient" Version="3.2.2" />
    <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="3.2.2" />
  </ItemGroup>

</Project>

Any ideas on what I am doing wrong?

I have tried different versions of the packages, different code in the Program.cs.


Solution

  • To fix the error, I uninstalled the Microsoft.Graph package and updated the code in Program.cs and HomeController.cs to retrieve user attributes from Entra using Microsoft Graph.

    .csproj :

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.10" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.Identity.Web" Version="2.19.1" />
      <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="2.19.1" />
      <PackageReference Include="Microsoft.Identity.Web.UI" Version="2.19.1" />
      <PackageReference Include="Microsoft.Identity.Web.DownstreamApi" Version="2.15.2" />
    </ItemGroup>
    

    Program.cs :

    using Microsoft.AspNetCore.Authentication.OpenIdConnect;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc.Authorization;
    using Microsoft.Identity.Web;
    using Microsoft.Identity.Web.UI;
    
    var builder = WebApplication.CreateBuilder(args);
    var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
    builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
            .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
                .AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
                .AddInMemoryTokenCaches();
    builder.Services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    });
    builder.Services.AddRazorPages()
        .AddMicrosoftIdentityUI();
    var app = builder.Build();
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    app.MapRazorPages();
    app.Run();
    

    HomeController.cs :

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Graph;
    using Microsoft.Identity.Web;
    
    namespace WebApplication15.Controllers
    {
        [Authorize]
        public class HomeController : Controller
        {
            private readonly GraphServiceClient _graphClient;
            public HomeController(GraphServiceClient graphClient)
            {
                _graphClient = graphClient;
            }
    
            [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")]
            public async Task<IActionResult> Index()
            {
                var user = await _graphClient.Me.Request().GetAsync();
                ViewData["GraphApiResult"] = user.UserPrincipalName;
                return View();
            }
            [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")]
            public async Task<IActionResult> GetUserDetails(string userId)
            {
               var result = await _graphClient.Users[userId]
                  .Request()
                  .Select("displayName,givenName,postalCode,identities")
                  .GetAsync();
               ViewData["DisplayName"] = result.DisplayName;
               ViewData["GivenName"] = result.GivenName;
               ViewData["PostalCode"] = result.PostalCode;
               ViewData["Identities"] = result.Identities;
              return View();
        }
        }
    }
    

    appsettings.json :

    {
      "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "<domain>",
        "TenantId": "<tenantID>",
        "ClientId": "<clientID>",
        "CallbackPath": "/signin-oidc"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*",
      "MicrosoftGraph": {
        "BaseUrl": "https://graph.microsoft.com/v1.0",
        "Scopes": "user.read User.ReadBasic.All"
      }
    }
    

    index.cshtml :

    @{
        ViewData["Title"] = "Home Page";
    }
    
    <div class="text-center">
        <h1 class="display-4">Welcome</h1>
        <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
    </div>
    
    <hr />
    
    <div>
        <h2>Graph API Result - Current User</h2>
        <div><strong>User Principal Name:</strong> @ViewData["GraphApiResult"]</div>
    </div>
    
    <hr />
    
    <div>
        <h2>Graph API Result - User Details</h2>
        <form method="get" action="/Home/GetUserDetails">
            <label for="userId">Enter User ID:</label>
            <input type="text" id="userId" name="userId" required />
            <button type="submit">Get User Details</button>
        </form>
    
        <div>
            <h3>User Details:</h3>
            <p><strong>Display Name:</strong> @ViewData["DisplayName"]</p>
            <p><strong>Given Name:</strong> @ViewData["GivenName"]</p>
            <p><strong>Postal Code:</strong> @ViewData["PostalCode"]</p>
    
            <h4>Identities:</h4>
            <ul>
                @if (ViewData["Identities"] is List<Microsoft.Graph.ObjectIdentity> identities && identities.Count > 0)
                {
                    foreach (var identity in identities)
                    {
                        <li>
                            <strong>Sign-in Type:</strong> @identity.SignInType <br />
                            <strong>Issuer:</strong> @identity.Issuer <br />
                            <strong>Issuer Assigned ID:</strong> @identity.IssuerAssignedId
                        </li>
                    }
                }
                else
                {
                    <li>No identities found.</li>
                }
            </ul>
        </div>
    </div>
    

    _UserDetails.cshtml :

    <p><strong>Display Name:</strong> @ViewData["DisplayName"]</p>
    <p><strong>Given Name:</strong> @ViewData["GivenName"]</p>
    <p><strong>Postal Code:</strong> @ViewData["PostalCode"]</p>
    
    <h4>Identities:</h4>
    <ul>
        @if (ViewData["Identities"] != null)
        {
            var identities = (System.Collections.Generic.IList<object>)ViewData["Identities"];
            foreach (var identity in identities)
            {
                <li>@identity</li>
            }
        }
        else
        {
            <li>No identities found.</li>
        }
    </ul>
    

    GetUserDetails.cshtml :

    @{
        ViewData["Title"] = "User Details";
    }
    
    <h2>User Details</h2>
    
    <p><strong>Display Name:</strong> @ViewData["DisplayName"]</p>
    <p><strong>Given Name:</strong> @ViewData["GivenName"]</p>
    <p><strong>Postal Code:</strong> @ViewData["PostalCode"]</p>
    
    <h4>Identities:</h4>
    <ul>
        @if (ViewData["Identities"] is List<Microsoft.Graph.ObjectIdentity> identities && identities.Count > 0)
        {
            foreach (var identity in identities)
            {
                <li>
                    <strong>Sign-in Type:</strong> @identity.SignInType <br />
                    <strong>Issuer:</strong> @identity.Issuer <br />
                    <strong>Issuer Assigned ID:</strong> @identity.IssuerAssignedId
                </li>
            }
        }
        else
        {
            <li>No identities found.</li>
        }
    </ul>
    

    Output :

    After successfully logging in, I got the below page. Then, I entered the user object ID and clicked on Get User Details as shown below.

    enter image description here

    User attributes :

    enter image description here