Skip to content

Multiple Authentication Schemes

Jean-Marc Prieur edited this page Jul 12, 2021 · 13 revisions

Multiple authentication schemes

Microsoft Identity Web now supports multiple authentication schemes, as of v.1.11.0.

This means, as an app developer, you can have several authentication schemes in the same ASP.NET Core app. Such as signing-in users with two identity providers (two Azure AD web app registration), or an Azure AD app and an Azure AD B2C app, or a web app and a web API. Basically mixing authentication schemes in the same ASP.NET Core app.

Example

See this developer test app, which shows how to have both an Azure AD B2C and Azure AD sign-in in the same app.

appsettings.json

In the appsettings.json you can now have two authentication schemes. In this example, we'll do one for Azure AD and one for Azure AD B2C. Both apps are registered in their respective portals.

{
    "AzureAdB2C": {
        "Instance": "https://fabrikamb2c.b2clogin.com",
        "ClientId": "fdb91ff5-5ce6-41f3-bdbd-8267c817015d",
        "Domain": "fabrikamb2c.onmicrosoft.com",
        "SignUpSignInPolicyId": "b2c_1_susi",
        "ResetPasswordPolicyId": "b2c_1_reset",
        "EditProfilePolicyId": "b2c_1_edit_profile", // Optional profile editing policy
        "CallbackPath": "/signin-oidc-b2c",
        "ClientSecret": "",
        "SignedOutCallbackPath": "/signout/B2C_1_susi"
    },
    "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "msidentitysamplestesting.onmicrosoft.com",
        "TenantId": "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab",
        "ClientId": "86699d80-dd21-476a-bcd1-7c1a3d471f75",
        "ClientSecret": "",
        "ClientCertificates": [
        ],
        "CallbackPath": "/signin-oidc"
    },
    "DownstreamApi": {
        "BaseUrl": "https://graph.microsoft.com/v1.0",
        "Scopes": "user.read"
    },
    "DownstreamB2CApi": {
        "BaseUrl": "https://fabrikamb2chello.azurewebsites.net/hello",
        "Scopes": "https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "AllowedHosts": "*"
}

Startup.cs

In Startup.cs in ConfigureServices, we have two sections for .AddAuthentication, one for AzureAd and one for AzureAdB2C. Please note that .AddAuthentication() has no default scheme defined.

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), OpenIdConnectDefaults.AuthenticationScheme)
            .EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '))
                .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
                .AddInMemoryTokenCaches();

services.AddAuthentication()
        .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"), "B2C", "cookiesB2C")
            .EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetValue<string>("DownstreamB2CApi:Scopes")?.Split(' '))
                .AddDownstreamWebApi("DownstreamB2CApi", Configuration.GetSection("DownstreamB2CApi"));

For the AAD sign-in, the web app will call Microsoft Graph, and for AzureAD B2C, the same ASP.NET Core web app will call a downstream B2C web API.

Controllers

For the test app, there are two Home controllers. The one for B2C will now specify the authentication scheme in the Authorize attribute, as this is not the default authorization scheme, and it will pass-in the authentication scheme to the methods acquiring tokens or calling the downstream API (IDownstreamWebApi)

 [Authorize(AuthenticationSchemes = "B2C")]
    public class HomeB2CController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IDownstreamWebApi _downstreamWebApi;

        public HomeB2CController(ILogger<HomeController> logger, IDownstreamWebApi downstreamWebApi)
        {
            _downstreamWebApi = downstreamWebApi;
            _logger = logger;
        }

        [AuthorizeForScopes(
            ScopeKeySection = "DownstreamB2CApi:Scopes", UserFlow = "b2c_1_susi")]
        public async Task<IActionResult> Index()
        {
            var value = await _downstreamWebApi.GetForUserAsync<Task>("DownstreamB2CApi", authenticationScheme:"B2C");
            return View(value);
        }
// more code here ...

In _Layout.cshtml:

// These lines added 
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="HomeB2C" asp-action="Index">Sign-in B2C</a>
</li>

Now, I can support both AAD and AAD B2C login with the same ASP.NET Core web app.

Troubleshooting

Cookie schemes

In the case of Blazor server @sven5 reported that they needed to use cookie authentication as the default authentication scheme, and then pass-in null as the cookieScheme parameters in AddMicrosoftIdentityWebApp(). For details see #549. Thanks to @sven5 for sharing their findings.

The code is then as follows:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
        options.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0);
});

services.AddAuthentication()
       .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"), Microsoft.Identity.Web.Constants.AzureAd, null);

services.AddAuthentication()
       .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"), Microsoft.Identity.Web.Constants.AzureAdB2C, null);

Backward compatibility

Support for multiple authentication schemes was introduced in Microsoft.Identity.Web 1.11.0. We've taken a lot of precautions to ensure the backward compatibility, but we didn't think of one scenario:

If you were injecting IOptions<MicrosoftIdentityOptions> in a controller before Microsoft.Identity.Web 1.11.0

 public OnboardingController(SampleDbContext dbContext, 
                             IOptions<MicrosoftIdentityOptions> microsoftIdentityOptions, 
                             IConfiguration configuration)
 {
  this.dbContext = dbContext;
  this.microsoftIdentityOptions = microsoftIdentityOptions.Value;
  this.configuration = configuration;
 }

you'll now need to inject an IOptionsMonitor<MicrosoftIdentityOptions>, and get the value corresponding to the authentication scheme (usually OpenIdConnectDefaults.AuthenticationScheme for web apps and JwtBearerDefaults.AuthenticationScheme for web APIs)

Therefore we need to have:

 public OnboardingController(SampleDbContext dbContext, 
                             IOptionsMonitor<MicrosoftIdentityOptions> microsoftIdentityOptions,
                             IConfiguration configuration)
 {
  this.dbContext = dbContext;
  this.microsoftIdentityOptions = microsoftIdentityOptions.Get(OpenIdConnectDefaults.AuthenticationScheme);
  this.configuration = configuration;
 }

Getting started with Microsoft Identity Web

Token cache serialization

Web apps

Web APIs

Daemon scenario

Advanced topics

Extensibility

Credential providers

FAQ

News

Contribute

Other resources

Clone this wiki locally