How to use SimpleProvider with my own MSAL C# code

1k Views Asked by At

I'm trying to use my own MSAL code to work together. Developed with .NET Core 5 MVC. I have similar problem as I found in below link. But I just don't know how to make it work with the proposed answer. Or in other words, I'm still confuse how this integration is done.

[It is mandatory to use the login component in order to use the other components]It is mandatory to use the login component in order to use the other components

[Quickstart for MSAL JS]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html

I also have read following article too: [Simple Provider Example]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html

[A lap around microsoft graph toolkit day 7]https://developer.microsoft.com/en-us/office/blogs/a-lap-around-microsoft-graph-toolkit-day-7-microsoft-graph-toolkit-providers/

is there someone can pointing to me more details explanation about how to archive this.

Can someone explains further below response further. How to do it. Where should I place the code and how to return AccessToken to SimpleProvider? enter image description here

Edited:

Update my question to be more precise to what I want besides on top of the question. Below is the code I used in Startup.cs to automatically trigger pop up screen when user using the web app. When using the sample provided, it is always cannot get access token received or userid data. Question 2: How to save or store token received in memory or cache or cookies for later use by ProxyController and its classes.

//Sign in link under _layouts.aspx
<a class="nav-link" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>

// Use OpenId authentication in Startup.cs
        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        // Specify this is a web app and needs auth code flow
        .AddMicrosoftIdentityWebApp(options =>
        {
            Configuration.Bind("AzureAd", options);

            options.Prompt = "select_account";
                            
            options.Events.OnTokenValidated = async context =>
            {
                var tokenAcquisition = context.HttpContext.RequestServices
                    .GetRequiredService<ITokenAcquisition>();

                var graphClient = new GraphServiceClient(
                    new DelegateAuthenticationProvider(async (request) =>
                    {
                        var token = await tokenAcquisition
                            .GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
                        
                        request.Headers.Authorization =
                            new AuthenticationHeaderValue("Bearer", token);
                    })
                );

                // Get user information from Graph
                try
                {
                    var user = await graphClient.Me.Request()
                        .Select(u => new
                        {
                            u.DisplayName,
                            u.Mail,
                            u.UserPrincipalName,
                            u.MailboxSettings
                        })
                        .GetAsync();

                    context.Principal.AddUserGraphInfo(user);
                }
                catch (ServiceException)
                {
                }

                // Get the user's photo
                // If the user doesn't have a photo, this throws
                try
                {
                    var photo = await graphClient.Me
                        .Photos["48x48"]
                        .Content
                        .Request()
                        .GetAsync();

                    context.Principal.AddUserGraphPhoto(photo);
                }
                catch (ServiceException ex)
                {
                    if (ex.IsMatch("ErrorItemNotFound") ||
                        ex.IsMatch("ConsumerPhotoIsNotSupported"))
                    {
                        context.Principal.AddUserGraphPhoto(null);
                    }
                }
            };

            options.Events.OnAuthenticationFailed = context =>
            {
                var error = WebUtility.UrlEncode(context.Exception.Message);
                context.Response
                    .Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}");
                context.HandleResponse();

                return Task.FromResult(0);
            };

            options.Events.OnRemoteFailure = context =>
            {
                if (context.Failure is OpenIdConnectProtocolException)
                {
                    var error = WebUtility.UrlEncode(context.Failure.Message);
                    context.Response
                        .Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}");
                    context.HandleResponse();
                }

                return Task.FromResult(0);
            };
        })
        // Add ability to call web API (Graph)
        // and get access tokens
        .EnableTokenAcquisitionToCallDownstreamApi(options =>
        {
            Configuration.Bind("AzureAd", options);
        }, GraphConstants.Scopes)
        // Add a GraphServiceClient via dependency injection
        .AddMicrosoftGraph(options =>
        {
            options.Scopes = string.Join(' ', GraphConstants.Scopes);
        })
        // Use in-memory token cache
        // See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization
        .AddInMemoryTokenCaches();
2

There are 2 best solutions below

5
On BEST ANSWER

Since you are using MVC, I recommend using the ProxyProvider over the Simple Provider.

  • SimpleProvider - useful when you have existing authentication on the client side (such as Msal.js)
  • ProxyProvider - useful when you are authenticating on the backend and all graph calls are proxied from the client to your backend.

This .NET core MVC sample might help - it is using the ProxyProvider with the components

0
On

Finally, I have discovered how to do my last mile bridging for these two technology.

Following are the lines of the code that I have made the changes. Since I'm using new development method as oppose by MSAL.NET, a lot of implementation has been simplified, so many of examples or article out there, may not really able to use it directly.

Besides using links shared by @Nikola and me above, you also can try to use below https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/ to consolidate to become your very own solution. Below are the changes I have made to make it worked.

Change in Startup.cs class

// Add application services. services.AddSingleton<IGraphAuthProvider, GraphAuthProvider>(); //services.AddSingleton<IGraphServiceClientFactory, GraphServiceClientFactory>();

Change in ProxyController.cs class

private readonly GraphServiceClient _graphClient; 

public ProxyController(IWebHostEnvironment hostingEnvironment, GraphServiceClient graphclient)
    {
        _env = hostingEnvironment;
        //_graphServiceClientFactory = graphServiceClientFactory;
        _graphClient = graphclient;
    }

Change in ProcessRequestAsync method under ProxyController.cs

 //var graphClient = _graphServiceClientFactory.GetAuthenticatedGraphClient((ClaimsIdentity)User.Identity);
       
        var qs = HttpContext.Request.QueryString;
        var url = $"{GetBaseUrlWithoutVersion(_graphClient)}/{all}{qs.ToUriComponent()}";

        var request = new BaseRequest(url, _graphClient, null)
        {
            Method = method,
            ContentType = HttpContext.Request.ContentType,
        };