asp.net-mvcasp.net-coreasp.net-identityopenid-connectopenid-provider

Use Microsoft Identity Platform as External Auth provider in AspnetCore Identity


I am successfully using Azure AD and Office365 as a login provider in AspNet-Core Identity by using Microsoft.AspnetCore.Authentication.OpenIdConnect and calling

AddRemoteScheme<OpenIdConnectOptions, OpenIdConnectHandler>("AzureAD","Office 365",_=> { })

I then add a PostConfigureOptions handler for the OpenIdConnectOptions to set it up to work with Azure. This adds a Login with Office 365 button to the login page and is working, but there must be an easier way.

I was curious to see if Microsoft.Identity.Web could be used instead, but am unable to get it to work quite right in my test.

Using the Aspnet-Core templates for dotnet 6 in VS 2022 and selecting individual accounts for authentication you are scaffolded a project with AspNet-Core Identity configured to use an IdentityDbContext with local accounts.

When running the app and logging in, you see an empty list of external authentication providers and a link to Microsoft documentation on adding external authentication providers here:

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-6.0&tabs=visual-studio

To test the Microsoft.Identity.Web package, I am calling:

builder.Services.AddAuthentication().AddMicrosoftIdentityWebApp(config.GetSection("AzureAD"))

in Programs.cs

This works to add the authentication provider and I now get an "OpenIdConnect" button under "Use another service to log in". Clicking it results in a failure "Error loading external login information."

When trying to login by clicking the button, I receive "Error loading external login information.". Line 107 in ExternalLogin.cshtml.cs from Microsoft's Identity UI is always null:

var info = await _signInManager.GetExternalLoginInfoAsync();

Is it possible to provide the right arguments to .AddMicrosoftIdentityWebApp() such that it works with AspNet-Core Identity as an external authentication provider using minimal code and configuration?


Solution

  • After working through the source, the solution was simple. Here is a working extension method that will configure Microsoft.Identity.Web for use as an eternal authentication provider with AspNet-Core Identity:

    public static class ServiceCollectionExtensions
    {
        public static IServiceCollection AddOffice365Identity<TContext,TUser>(
            this IServiceCollection services,
            IConfigurationSection config,
            Action<IdentityOptions>? options = null)
              where TContext : IdentityDbContext
              where TUser : class
        {
    
          //Setup Default Identity
          var identityBuilder = options == null ?
            services.AddDefaultIdentity<TUser>()
            : services.AddDefaultIdentity<TUser>(options);
        
          identityBuilder.AddEntityFrameworkStores<TContext>();
    
         //Add Microsoft.Identity.Web 
         services
            .AddAuthentication()
            .AddMicrosoftIdentityWebApp(config,displayName: "Office 365")
            .EnableTokenAcquisitionToCallDownstreamApi()
            .AddMicrosoftGraph()
            .AddInMemoryTokenCaches();
    
          //This is the important part: Change the SignInScheme to External
          //After ALL other configuration have run.
          //Claim mapper maps all claims and adds the ClaimTypes.Email claim.
          services
            .AddOptions()
            .PostConfigureAll<OpenIdConnectOptions>(o => {
            o.SignInScheme = IdentityConstants.ExternalScheme;            
            o.ClaimActions.Add(new ClaimMapper());
          });
    
          return services;
      }
    }
    

    Running this you will end up with a working "Office 365" configuration assuming your appconfig has the usual:

    "AzureAd": {
      "CallbackPath": "/signin-oidc",
      "ClientSecret": "[client_secret]",
      "ClientId": "[client_id]",
      "Domain": "[domain]",
      "Instance": "https://login.microsoftonline.com/",
      "SignedOutCallbackPath": "/signout-callback-oidc",
      "TenantId": "[Tenant Id]"
    }