Description
Issue
Today the template introduce files to easy calling MIcrosoft Graph that would benefit from being productized in the library
Spec
See #431
We'd want to be able to set which Microsoft graph endpoint to use (v1.0, Beta, but also national clouds) from the configuration, or programmatically. For instance in the appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "qualified.domain.name",
"TenantId": "22222222-2222-2222-2222-222222222222",
"ClientId": "11111111-1111-1111-11111111111111111",
"ClientSecret": "[Copy the client secret added to the app from the Azure portal]",
"CallbackPath": "/signin-oidc"
},
"GraphBeta": {
"CalledApiUrl": "https://graph.microsoft.com/beta",
"CalledApiScopes": "user.read"
},
}
And be able to reference them in a CallsMicrosoftGraph
method that would be called under CallsWebApi()
or under AddMicrosoftIdentityWebApp
or AddMicrosoftIdentityWebApi
(to be decided)
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.CallsWebApi()
.CallsMicrosoftGraph(Configuration.GetSection("GraphBeta"))
.AddInMemoryTokenCaches();
We'll also provide a simple override that use the default (https://graph.microsoft.com/v1.0 and "user.read")
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityPlatformWebApp(Configuration.GetSection("AzureAd"))
.CallsWebApi(initialScopes:new string[] {"user.read" })
.CallsMicrosoftGraph()
.AddInMemoryTokenCaches();
And an override that offers a delegate:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityPlatformWebApp(Configuration.GetSection("AzureAd"))
.CallsWebApi(initialScopes:new string[] {"user.read" })
.CallsMicrosoftGraph("GraphBeta",
options => options.CalledApiBaseUrl = "https://graph.microsoft.com/beta")
.AddInMemoryTokenCaches();
In the controller or a Razor page, or a Blazor page, the GraphServiceClient will be injected by dependency injection and simply used.
using Microsoft.Identity.Web;
namespace WebAppCallsMicrosoftGraph.Pages
{
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
public class IndexModel : PageModel
{
private readonly GraphServiceClient _graphServiceClient;
public IndexModel(ILogger<IndexModel> logger, GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
public async Task OnGet()
{
var user = await _graphServiceClient.Me.Request().GetAsync();
try
{
using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
{
byte[] photoByte = ((MemoryStream)photoStream).ToArray();
ViewData["photo"] = Convert.ToBase64String(photoByte);
}
ViewData["name"] = user.DisplayName;
}
catch (Exception)
{
ViewData["photo"] = null;
}
}
}
}
Proposed design / work to be done
- Add a folder named DownstreamApiSupport
- Add a CalledApiOptions class, which will also be used for Downstream APIs (hence the HttpMethod which does not seem to be used here)
/// <summary>
/// Options passed-in to call web APIs (Microsoft Graph or another API).
/// </summary>
public class CalledApiOptions
{
/// <summary>
/// Base URL for the called Web API. For instance <c>"https://graph.microsoft.com/v1.0/".</c>.
/// </summary>
public string CalledApiBaseUrl { get; set; } = "https://graph.microsoft.com/v1.0/";
/// <summary>
/// Space separated scopes used to call the Web API.
/// For instance "user.read mail.read".
/// </summary>
public string? CalledApiScopes { get; set; } = null;
/// <summary>
/// Http method used to call this API (by default Get).
/// </summary>
public HttpMethod HttpMethod { get; set; } = HttpMethod.Get;
}
- Add the
TokenAcquisitionCredentialProvider
class which is currently shipped in the templates. - Reference the
Microsoft.Graph
NuGet package from the Microsoft.Identity.Web project - Remove references to the
Microsoft.Graph
NuGet package from all the test apps that use it - Add a
MicrosoftGraphServiceExtensions
inspired by the one shipped in the templates, but applied to
/// <summary>
/// Extensions methods on a MicrososoftAppCallingWebApiAuthenticationBuilder builder
/// to add support to call Microsoft Graph.
/// </summary>
public static class MicrosoftGraphServiceExtensions
{
/// <summary>
/// Add support to calls Microsoft graph. From a named option and a configuration section
/// </summary>
/// <param name="builder">Builder.</param>
/// <param name="configurationSection">Configuraiton section.</param>
/// <returns>The builder to chain.</returns>
public static MicrososoftAppCallingWebApiAuthenticationBuilder CallsMicrosoftGraph(
this MicrososoftAppCallingWebApiAuthenticationBuilder builder,
IConfigurationSection configurationSection)
{
// Todo chain with the one with delegates
}
/// <summary>
/// Add support to calls Microsoft graph. From a base graph Url and a default scope.
/// </summary>
/// <param name="builder">Builder.</param>
/// <param name="graphBaseUrl">Named instance of option.</param>
/// <param name="defaultScopes">Configuration section.</param>
/// <returns>The builder to chain.</returns>
public static MicrososoftAppCallingWebApiAuthenticationBuilder CallsMicrosoftGraph(
this MicrososoftAppCallingWebApiAuthenticationBuilder builder,
string graphBaseUrl = "https://graph.microsoft.com/v1.0",
string defaultScopes = "user.read")
{
// Todo chain with the one with delegates
}
/// <summary>
/// Add support to calls Microsoft graph. From a named options and a configuraiton method.
/// </summary>
/// <param name="builder">Builder.</param>
/// <param name="configureOptions">Method to configure the options.</param>
/// <returns>The builder to chain.</returns>
public static MicrososoftAppCallingWebApiAuthenticationBuilder CallsMicrosoftGraph(
this MicrososoftAppCallingWebApiAuthenticationBuilder builder,
Action<CalledApiOptions> configureOptions);
-
The override with delegates will need to add the following services:
- Options (
builder.Services.AddOptions<CalledApiOptions>().Configure(configureOptions)
) - TokenAcquisition (as a singleton) (
builder.Services.AddTokenAcquisition(true)
) - GraphServiceClient (as a singleton) (builder.Services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider => {`
- Options (
-
Update the test apps to use this new way of adding Graph (in the Startup.cs / appsettings.json)
-
Update the project templates to use this new way of adding Graph (and remove the references to the Microsoft.Graph NuGet package)