Skip to content

[Feature Request] CallsMicrosoftGraph in Startup.cs enables usage of the Graph SDK in controllers / Razor / Blazor pages #427

Closed
@jmprieur

Description

@jmprieur

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 => {`
  • 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)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions