This post shows how Microsoft Graph API can be used in both ASP.NET Core UI web applications and also ASP.NET Core APIs for delegated identity flows. The ASP.NET Core applications are secured using Microsoft.Identity.Web. In the API project, the Graph API client is used in a delegated flow with user access tokens getting an access token for the graph API on behalf of the identity created from the access token used to request the API.
Code: https://github.com/damienbod/AspNetCoreUsingGraphApi
Using Graph API from an ASP.NET Core UI application
Using the Graph API client in an ASP.NET Core UI web application can be implemented using the Microsoft.Identity.Web.MicrosoftGraph nuget package. This can be added to the project file as well as the Azure authentication packages. If using the beta version, switch to the Microsoft.Identity.Web.MicrosoftGraphBeta nuget package.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<UserSecretsId>your-secret-4342390023dd02de2d</UserSecretsId>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference
Include="Microsoft.AspNetCore.Authentication.JwtBearer"
Version="7.0.2" NoWarn="NU1605" />
<PackageReference
Include="Microsoft.AspNetCore.Authentication.OpenIdConnect"
Version="7.0.2" NoWarn="NU1605" />
<PackageReference Include="Swashbuckle.AspNetCore"
Version="6.5.0" />
<PackageReference
Include="Microsoft.Identity.Web" Version="1.25.10" />
<PackageReference
Include="Microsoft.Identity.Web.UI" Version="1.25.10" />
<PackageReference
Include="Microsoft.Identity.Web.MicrosoftGraphBeta"
Version="1.25.10" />
</ItemGroup>
</Project>
The application authentication and the authorization are setup in the Startup class. The AddMicrosoftGraph method is used to add the required scopes for your Graph API calls. The AddMicrosoftIdentityWebAppAuthentication method is used in the UI ASP.NET Core application.
public void ConfigureServices(IServiceCollection services)
{
string[]? initialScopes = Configuration.GetValue<string>
("CallApi:Scopes")?.Split(' ');
services.AddHttpClient();
services.AddScoped<GraphApiClientUI>();
services.AddScoped<ApiService>();
var baseAddress = Configuration["GraphApi:BaseUrl"];
baseAddress ??= "https://graph.microsoft.com/beta";
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(baseAddress,
"https://graph.microsoft.com/.default")
.AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
The GraphServiceClient service can be added directly in the services and used to access the Graph API. This is really simple in a UI application and you don’t need to handle token requests or anything else like this, all is implemented in the Microsoft.Identity.Web packages. But if you require special scopes or would like to handle this yourself, this is possible and the GraphServiceClient instance can be created as shown below.
public class GraphApiClientUI
{
private readonly GraphServiceClient _graphServiceClient;
public GraphApiClientUI(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task<User> GetGraphApiUser()
{
return await _graphServiceClient
.Me
.Request()
.GetAsync();
}
The Graph API data can be returned in the UI views of the ASP.NET Core application.
[Authorize]
public class HomeController : Controller
{
private readonly GraphApiClientUI _graphApiClientUI;
public HomeController(GraphApiClientUI graphApiClientUI)
{
_graphApiClientUI = graphApiClientUI;
}
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> Index()
{
var user = await _graphApiClientUI.GetGraphApiUser();
ViewData["ApiResult"] = user.DisplayName;
return View();
}
Using Graph API from an ASP.NET Core API
Using Graph API from an ASP.NET Core API application is different to a UI application. The Graph API is called on behalf of the identity created from the access token calling the API. This is a delegated user access token. The Azure AD client security for the API can be setup using the AddMicrosoftIdentityWebApiAuthentication method.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddScoped<GraphApiClientDirect>();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
// .RequireClaim("email")
// disabled this to test with users that have no email
// (no license added)
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSwaggerGen(c =>
{
// add JWT Authentication
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter JWT Bearer token **_only_**",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer", // must be lower case
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{securityScheme, Array.Empty<string>()}
});
c.SwaggerDoc("v1", new OpenApiInfo
{ Title = "Web API using Graph API", Version = "v1" });
});
}
In the service responsible for implementing the Graph API client, the ITokenAcquisition interface is required as well as the IHttpClientFactory interface. When creating new instances of the GraphServiceClient, the IHttpClientFactory interface is used to create the HttpClient which is used by the Graph API client.
A new access token is requested for the required scopes using the GetAccessTokenForUserAsync method. This returns a delegated access token and the token is then used in the DelegateAuthenticationProvider. The GraphServiceClient client is created using the HttpClient which was created using the IHttpClientFactory _clientFactory. The lifecycle of the HttpClients are handled correctly then.
private async Task<GraphServiceClient> GetGraphClient(string[] scopes)
{
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
var client = _clientFactory.CreateClient();
client.BaseAddress = new Uri("https://graph.microsoft.com/beta");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var graphClient = new GraphServiceClient(client)
{
AuthenticationProvider
= new DelegateAuthenticationProvider((requestMessage) => {
requestMessage.Headers.Authorization
= new AuthenticationHeaderValue("bearer", token);
return Task.CompletedTask;
}),
BaseUrl = "https://graph.microsoft.com/beta"
};
return graphClient;
}
The Graph API client can then be used to request data from Azure Microsoft Graph API.
public async Task<User> GetGraphApiUser()
{
var graphclient = await GetGraphClient(
new string[] { "User.ReadBasic.All", "user.read" });
return await graphclient.Me.Request().GetAsync();
}
This could also be used to request files from Sharepoint or any other resource made available through the Graph rest APIs.
public async Task<string> GetSharepointFile()
{
var graphclient = await GetGraphClient(
new string[] { "user.read", "AllSites.Read" }
);
var user = await graphclient.Me.Request().GetAsync();
if (user == null)
throw new ArgumentException($"User not found in AD.");
var sharepointDomain = "damienbodtestsharing.sharepoint.com";
var relativePath = "/sites/TestDoc";
var fileName = "aad_ms_login_02.png";
var site = await graphclient
.Sites[sharepointDomain]
.SiteWithPath(relativePath)
.Request()
.GetAsync();
var drive = await graphclient
.Sites[site.Id]
.Drive
.Request()
.GetAsync();
var items = await graphclient
.Sites[site.Id]
.Drives[drive.Id]
.Root
.Children
.Request()
.GetAsync();
var file = items.FirstOrDefault(f => f.File != null
&& f.WebUrl.Contains(fileName));
var stream = await graphclient
.Sites[site.Id]
.Drives[drive.Id]
.Items[file!.Id].Content
.Request()
.GetAsync();
var fileAsString = StreamToString(stream);
return fileAsString;
}
The Graph API client service can then be used in the API which is protected using the Microsoft.Identity.Web packages.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace WebApiUsingGraphApi.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class GraphCallsController : ControllerBase
{
private readonly GraphApiClientDirect _graphApiClientDirect;
public GraphCallsController(GraphApiClientDirect graphApiClientDirect)
{
_graphApiClientDirect = graphApiClientDirect;
}
[HttpGet]
public async Task<string> Get()
{
var user = await _graphApiClientDirect.GetGraphApiUser();
// var photo = await _graphApiClientDirect.GetGraphApiProfilePhoto();
// var file = await _graphApiClientDirect.GetSharepointFile();
return user.DisplayName;
}
}
When using the Graph API client in ASP.NET Core applications for delegated access user access tokens, the correct initialization should be used. If creating the instance yourself, use the IHttpClientFactory to create the HttpClient instance used in the client. If you create Graph API requests using different scopes, you would also need to use the ITokenAcquisition and the IHttpClientFactory interfaces to create the GraphApiClient.

Links:
https://developer.microsoft.com/en-us/graph/
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient
[…] Using Microsoft Graph API in ASP.NET Core (Damien Bowden) […]
[…] Using Microsoft Graph API in ASP.NET Core – Damien Bowden […]
Nice post!
How we can update password profile using C#.NEt?
Thank you so much for the post! I followed your instruction for my Web API, however I’m getting following error when creating the graph service client. Am I missing anything?
“IDW10104: Both client secret and client certificate cannot be null or whitespace, and only ONE must be included in the configuration of the web app when calling a web API. For instance, in the appsettings.json file. ”
I have ClientId in my AzureAd section of the appSettings.json.
The error is happening on this line
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(
scopes).ConfigureAwait(false);
[…] Using Microsoft Graph API in ASP.NET Core […]
[…] Using Microsoft Graph API in ASP.NET Core […]