Search code examples
asp.net-corerazorusermanager

Setting up asp.net Razor page UserManager for Dependency Injection


I understand UserManager for an asp.net Razor page is defined via dependency injection and so I have a page as follows:

public class SetupModel : PageModel
{
      public SetupModel(UserManager<IdentityUser> userManager)
      {
      }

Unsurprisingly with no other config in Program.cs this page blows up with 'InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager'

My question is how to setup UserManager in Program.cs for DI?

ChatGPT says it is simply:

builder.services.AddScoped<UserManager<IdentityUser>>(); 

but this results in an error: "Some services are not able to be constructed....Unable to resolve service for type Microsoft.AspNetCore.Identity.IUserStore"

The only Authentication code in Program.cs comes from the boilerplate Visual Studio creates for Authentication: Microsoft Identity Platform, shown below. What do I have to modify or add to config UserManager?

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddRazorPages()
     .AddMicrosoftIdentityUI();

P.S. I do see the SSO user in @User.Identity?.Name! so Authentication is working but now I would like to see the roles that user is in.


Per Tiny Wang's advice below my code now looks like this for a .Net 8 Razor page app using Microsoft Identity Platform with https. It results in an exception: The service collection cannot be modified because it is read-only.

    public static void Main(string[] args) {
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    // From StackOverflow Tiny Wang
     builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi()
        .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
        .AddInMemoryTokenCaches();
    
    builder.Services.AddAuthorization(options => {
        options.FallbackPolicy = options.DefaultPolicy;
    });
    builder.Services.AddRazorPages()
        .AddMicrosoftIdentityUI();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (!app.Environment.IsDevelopment()) {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapRazorPages();
    app.MapControllers();
    
    app.Run();

and my appsettings.json file:

{
  // This is for SSO using VS Authentication/Identity Platform
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "obfuscated.com",
    "TenantId": "2dc6507f-***obfuscated***524a1",
    "ClientId": "fbc512fe-***obfuscated****",
    "ClientSecret": "gxU8Q***obfuscated***",
    "CallbackPath": "/signin-oidc"
  },
  "DownstreamApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "User.ReadWrite.All"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

and my nuget versions:

microsoft.identity.web.ui\1.16.0\
microsoft.aspnetcore.authentication.openidconnect\6.0.24\
microsoft.identity.web\1.16.0\
microsoft.graph\4.43.0\
microsoft.aspnetcore.authentication.jwtbearer\6.0.24\
microsoft.identity.web.microsoftgraph\1.16.0\

For anyone following this thread the final solution was to upgrade all nupkg to the latest version and then replace microsoft.identity.web.microsoftgraph with Microsoft.Identity.Web.GraphServiceClient


Solution

  • In your code snippet, you had code below which means you are using Azure AD as the authentication scheme in your asp.net core web app, so that we are not able to use the UserManager which is a service provided by Asp.net core default identity. In your scenario, you can use Graph API for Users to access and manage user profile.

    builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
      .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
    

    Here's a sample for integrating Graph API SDK into your app and use the SDK to call API.

    builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
        .EnableTokenAcquisitionToCallDownstreamApi()
        .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
        .AddInMemoryTokenCaches();
    
    builder.Services.AddAuthorization(options =>
    {
        // By default, all incoming requests will be authorized according to the default policy.
        options.FallbackPolicy = options.DefaultPolicy;
    });
    builder.Services.AddRazorPages()
        .AddMicrosoftIdentityUI();
    

    Then in the PageModel, inject GraphServiceClient to call graph api.

    public class IndexModel : PageModel
    {
        private readonly ILogger<IndexModel> _logger;
        private readonly GraphServiceClient _graphServiceClient;
        public string? OfficeInfo { get; set; }
    
        public IndexModel(GraphServiceClient graphServiceClient, ILogger<IndexModel> logger)
        {
            _graphServiceClient = graphServiceClient;
            _logger = logger;
        }
    
        public async Task OnGetAsync()
        {
            var me = await _graphServiceClient.Me.Request().GetAsync();
            OfficeInfo = me.OfficeLocation;
        }
    }
    

    By the way, we need to add in appsettings.json with client secret and DownstreamApi.

    "AzureAd": {
      "Instance": "https://login.microsoftonline.com/",
      "Domain": "tenant_id",
      "TenantId": "tenant_id",
      "ClientId": "client_id",
      "ClientSecret": "client_secret",
      "CallbackPath": "/signin-oidc"
    },
    "DownstreamApi": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": "User.ReadWrite.All"
    },
    

    Here's the nuget packages I used. Pls note, Microsoft.Graph in Version 5.x would bring differences in coding.

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.24" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.24" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.Identity.Web" Version="1.16.0" />
      <PackageReference Include="Microsoft.Identity.Web.UI" Version="1.16.0" />
        <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.16.0" />
        <PackageReference Include="Microsoft.Graph" Version="4.43.0" />
    </ItemGroup>
    

    enter image description here

    In my code snippet above, I'm using this API which requires User.Read permission, so I need to add it in Azure.

    enter image description here

    ====================== With .net 8 and Graph SDK v5.x ============

    We need to change the nuget package versions and modify the API calling code. The rest are the same.

    public async Task OnGetAsync()
    {
        var me = await _graphServiceClient.Me.GetAsync();
        OfficeInfo = me.OfficeLocation;
    }
    
    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.0" NoWarn="NU1605" />
      <PackageReference Include="Microsoft.Identity.Web" Version="2.15.2" />
      <PackageReference Include="Microsoft.Identity.Web.UI" Version="2.15.2" />
      <PackageReference Include="Microsoft.Identity.Web.DownstreamApi" Version="2.15.2" />
        <!--<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="2.15.2" />-->
        <PackageReference Include="Microsoft.Graph" Version="5.44.0" />
        <PackageReference Include="Microsoft.Identity.Web.GraphServiceClient" Version="2.15.2" />
    </ItemGroup>
    

    enter image description here