Authentication with multiple identity providers in ASP.NET Core

This article shows how to implement authentication in ASP.NET Core using multiple identity providers or secure token servers. When using multiple identity providers, the authentication flows need to be separated per scheme for the sign-in flow and the sign-out flow. The claims are different and would require mapping logic depending on the authorization logic of the application.

Code: https://github.com/damienbod/MulitipleClientClaimsMapping

Setup

OpenID Connect is used for the authentication and the session is stored in a cookie. A confidential client using OpenID Connect code flow with PKCE is used for both schemes. The client configuration in the secure token servers need to match the ASP.NET Core configuration. The sign-in and the sign-out callback URLs are different for the different token servers.

The AddAuthentication method is used to define the authentication services. Cookies are used to store the session. The “t1” scheme is used to setup the Duende OpenID Connect client and the “t2” scheme is used to setup the OpenIddict scheme. The callback URLs are specified in this setup.

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("t1", options => // Duende IdentityServer 
{
    builder.Configuration.GetSection("IdentityServerSettings").Bind(options);
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name"
    };
    options.MapInboundClaims = false;
})
.AddOpenIdConnect("t2", options => // OpenIddict server 
{
    builder.Configuration.GetSection("IdentityProviderSettings").Bind(options);
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name"
    };
});

The configurations which are different per environment are read from the configuration object. The source of the data can be the appsettings.json, Azure Key Vault, user secrets or whatever you use.

"IdentityProviderSettings": { // OpenIddict
	"Authority": "https://localhost:44318",
	"ClientId": "codeflowpkceclient",
	"ClientSecret": "--your-secret-from-keyvault-or-user-secrets--",
	"CallbackPath": "/signin-oidc-t2",
	"SignedOutCallbackPath": "/signout-callback-oidc-t2"
},
"IdentityServerSettings": { // Duende IdentityServer
	"Authority": "https://localhost:44319",
	"ClientId": "oidc-pkce-confidential",
	"ClientSecret": "--your-secret-from-keyvault-or-user-secrets--",
	"CallbackPath": "/signin-oidc-t1",
	"SignedOutCallbackPath": "/signout-callback-oidc-t1"
}

Sign-in

The application and the user can authenticate using different identity providers. The scheme is setup when starting to authentication flow so that the application knows which secure token server should be used. I added two separate controller endpoints for this. The Challenge request is then sent correctly.

[HttpGet("LoginOpenIddict")]
public ActionResult LoginOpenIddict(string returnUrl)
{
	return Challenge(new AuthenticationProperties
	{
		RedirectUri = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/", 
	}, "t2");
}

[HttpGet("LoginIdentityServer")]
public ActionResult LoginIdentityServer(string returnUrl)
{
	return Challenge(new AuthenticationProperties
	{
		RedirectUri = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/"
	}, "t1");
}

The UI part of the application calls the correct endpoint. This is just a HTTP link which sends a GET request.

<li class="nav-item">
    <a class="nav-link text-dark" 
       href="~/api/Account/LoginIdentityServer">Login t1 IdentityServer</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" 
       href="~/api/Account/LoginOpenIddict">Login t2 OpenIddict</a>
</li>

Sign-out

The application also needs to sign-out correctly. A sign-out is sent to the secure token server and not only locally in the application. To sign out correctly, the application must use the correct scheme. This can be found using the HttpContext features. Once the scheme is known, the sign-out request can be sent to the correct secure token server.

[Authorize]
public class LogoutModel : PageModel
{
    public async Task<IActionResult> OnGetAsync()
    {
        if (User.Identity!.IsAuthenticated)
        {
            var authProperties = HttpContext.Features
               .GetRequiredFeature<IAuthenticateResultFeature>();

            var schemeToLogout = authProperties.AuthenticateResult!.Ticket!
               .Properties.Items[".AuthScheme"];

            if (schemeToLogout != null)
            {
                return SignOut(new AuthenticationProperties
                {
                    RedirectUri = "/SignedOut"
                },
                CookieAuthenticationDefaults.AuthenticationScheme,
                schemeToLogout);
            }
        }

        await HttpContext.SignOutAsync(
          CookieAuthenticationDefaults.AuthenticationScheme);

        return Redirect("/SignedOut");
    }
}

Notes

Setting up multiple secure token servers or identity providers for a single ASP.NET Core application is relatively simple using the standard ASP.NET Core endpoints. Once you start using the different identity provider authentication Nuget client packages from the specific libraries, it gets complicated as the client libraries overwrite different default values which breaks the other client flows. Using multiple identity providers, it is probably better to not use the client libraries and stick to the standard OpenID Connect implementation.

Links

https://learn.microsoft.com/en-us/aspnet/core/security

https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme

https://github.com/damienbod/aspnetcore-standup-authn-authz

https://github.com/damienbod/aspnetcore-standup-securing-apis

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims

2 comments

  1. […] Authentication with multiple identity providers in ASP.NET Core (Damien Bowden) […]

  2. […] Authentication with multiple identity providers in ASP.NET Core – Damien Bowden […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.