简体   繁体   中英

In a Blazor Server app, is it possible to inject a GraphServiceClient into a scoped service?

I've been experimenting with the new Blazor features and I'm attempting to pull user data from our Azure AD into a test app. These are the relevant snippets:

My Service

public class UserService
{

    GraphServiceClient _graphClient { get; set; }
    protected User _user = null;

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

    public string GetUserName()
    {
        return User()?.DisplayName ?? "";
    }

Startup

public void ConfigureServices(IServiceCollection services)
    {
        var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');

        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)

            // Add sign-in with Microsoft
            .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))

            // Add the possibility of acquiring a token to call a protected web API
            .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)

                   // Enables controllers and pages to get GraphServiceClient by dependency injection
                   // And use an in memory token cache
                   .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
                   .AddInMemoryTokenCaches();

        services.AddControllersWithViews()
            .AddMicrosoftIdentityUI();

        services.AddRazorPages()
            .AddMicrosoftIdentityUI();
        services.AddServerSideBlazor()
            .AddMicrosoftIdentityConsentHandler();

        services.AddSingleton<WeatherForecastService>();
        services.AddScoped<UserService>();

The GraphServiceClient does get initialized in my.cs script but I get the error message:

Error: No account or login hint was passed to the AcquireTokenSilent call

Its not a problem (I think) with any azure configuration as everything works fine if I use the Microsoft sample and make a ComponentBase.

public class UserProfileBase : ComponentBase
{
    [Inject]
    GraphServiceClient GraphClient { get; set; }

    protected User _user = new User();
    protected override async Task OnInitializedAsync()
    {
        await GetUserProfile();
    }

    /// <summary>
    /// Retrieves user information from Microsoft Graph /me endpoint.
    /// </summary>
    /// <returns></returns>
    private async Task GetUserProfile()
    {
        try
        {
            var request = GraphClient.Me.Request();
            _user = await request.GetAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

My current thought is that the Authorize tag that the profile component uses (and thus the ComponentBase?) is doing something behind the scenes with the access token even though I am already authenticated?

@page "/profile"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inherits UserProfileBase
public class UserAccount
{
 public int Id {get; set;}
 public string FirstName {get; set;}
 public string LastName {get; set;}
 public string Email {get; set;}
}
public class UserService
{

  GraphServiceClient _graphClient;
  protected UserAccount _user {get; set;}

  public UserService(GraphServiceClient graphClient)
  {
    _graphClient = graphClient;
  }
  public async Task<string> GetUserName()
  {
    UserAccount = await GetUserAsync();
    return $"{UserAccount.FirstName} {UserAccount.LastName}";
  }
  public async Task<UserAccount> GetUserAsync()
  {
    var user = awiat __graphClient.Me.Request.Select( e => new
       {
         e.Id,
         e.GivenName,
         e.Surname,
         e.Identities,
       }).GetAsync();
    if(user != null)
    {
      var email = user.Identities.ToList().FirstOrDefault(x => x.SignInType == "emailAddress")?.IssuerAssignedId;
      return new UserAccount
         {
           Id= user.Id,
           FirstName= user.GivenName,
           LastName= user.Surname,
           Email= email
         };
    }
    else {return null;}
  }
}

Sorry this answer is over a year late - hopefully this will help someone else in the future. I was trying to solve this exact issue today too, and I got the missing pieces of this puzzle from this demo project .

In addition to your ConfigureServices method, you need to make sure that you have controllers mapped in your endpoints so that the Identity UI can map responses.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers(); // Important for Microsoft Identity UI
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

And then you need to have some exception handling on your scoped service. I'm pretty sure this is because of Blazor's pre-rendering feature not initially authenticating the user in the first render. But don't quote me on that

I can't see enough of the OP's service, so here's mine:

using Microsoft.Graph;
using Microsoft.Identity.Web;

namespace MyProject.Services
{
    public class UserService
    {
        private readonly GraphServiceClient _graphServiceClient;
        private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;

        public UserService(
            GraphServiceClient graphServiceClient,
            MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
        {
            _graphServiceClient = graphServiceClient;
            _consentHandler = consentHandler;
        }

        public async Task<User?> GetUserAsync()
        {
            try
            {
                return await _graphServiceClient.Me.Request().GetAsync();
            }
            catch (Exception ex)
            {
                _consentHandler.HandleException(ex);
                return null;
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM