Use multiple identity providers from a Blazor WASM ASP.NET Core App secured using BFF

This post shows how to implement a Blazor WASM UI hosted in an ASP.NET Core application using multiple identity providers to authenticate. Two confidential OpenID Connect code flow clients with PKCE are used to implement the Blazor authentication. The Blazor WASM and the ASP.NET Core application are a single security context. This is implemented using the backend for frontend security architecture.

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

Setup

Two identity providers are implemented using OpenIddict with ASP.NET Core Identity. Any OpenID Connect identity provider can be used here. The identity provider implements a confidential client requiring a secret. The OpenID Connect code flow is used to implement the authentication flow.


The Blazor application was setup using the Blazor.BFF.OpenIDConnect.Template which can be installed and used from Visual Studio or the dotnet cmd tool. The template creates an ASP.NET Core application with a WASM view and default definitions for OIDC code flow.

Blazor authentication

The Blazor applicaiton requires two OIDC client implementations. The user of the application can choose the required IDP. The DefaultChallengeScheme is set to “UNKNOWN” and this has no definition. The login or sign-in needs to set the authentication scheme for the challenge. The redirect Url and the logout URL needs to be explicitly set and cannot match a definition from a different challenge.

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = "UNKNOWN";
})
.AddCookie()
.AddOpenIdConnect("T1", options =>
{
    configuration.GetSection("OpenIDConnectSettingsT1").Bind(options);

    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.CallbackPath = "/signin-oidc-t1";
    options.SignedOutCallbackPath = "/signout-callback-oidc-t1";
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add("profile");
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
})
.AddOpenIdConnect("T2", options =>
{
    configuration.GetSection("OpenIDConnectSettingsT2").Bind(options);

    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.CallbackPath = "/signin-oidc-t2";
    options.SignedOutCallbackPath = "/signout-callback-oidc-t2";
    options.SaveTokens = true;
    options.Scope.Add("profile");
    options.GetClaimsFromUserInfoEndpoint = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
});

The AccountController implements the login and the logout requests. The login needs to force the login for the correct scheme. I named my schemes T1 and T2.

[HttpGet("LoginT1")]
public IActionResult T1(string returnUrl)
{
	var redirectUrl = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/";
	return Challenge(new AuthenticationProperties 
		{ RedirectUri = redirectUrl }, "T1");
}

[HttpGet("LoginT2")]
public IActionResult T2(string returnUrl)
{
	var redirectUrl = !string.IsNullOrEmpty(returnUrl) ? returnUrl : "/";
	return Challenge(new AuthenticationProperties 
		{ RedirectUri = redirectUrl }, "T2");
}

The CallbackPath needs to be unique in the application and a separated definition is required for each identity provider.

Blazor client logout

The logout works a bit like the login. You need to have a unique SignedOutCallbackPath definition for each scheme. I added a new claim to the id_token token from the identity provider. I added this claim in the OpenIddict server. Once the user is authenticated, the idp claim can used to verify which identity provider was used for authentication. The claim is required to logout which sends a request to the correct identity provider.

[ValidateAntiForgeryToken]
[Authorize]
[HttpPost("Logout")]
public IActionResult Logout()
{
	var authProperties = new AuthenticationProperties
	{
		RedirectUri = "/"
	};

	// custom claims added to idp, you need to 
	// implement something on your idp for this
	var usedT1ForAuthn = User.Claims.Any(
		idpClaim => idpClaim.Type == "idp" && idpClaim.Value == "T1");
	var usedT2ForAuthn = User.Claims.Any(
		idpClaim => idpClaim.Type == "idp" && idpClaim.Value == "T2");

	if(usedT1ForAuthn) 
		return SignOut(authProperties, 
		CookieAuthenticationDefaults.AuthenticationScheme, "T1");

	if (usedT2ForAuthn)
		return SignOut(authProperties, 
		CookieAuthenticationDefaults.AuthenticationScheme, "T2");

	return SignOut(authProperties, 
	CookieAuthenticationDefaults.AuthenticationScheme);
}

The custom “idp” is also used to display identity provider in the Blazor WASM UI.

<Authorized>
	<strong>@context?.User?.Identity?.Name idp: 
		@context?.User?.Claims.FirstOrDefault(c => c.Type == "idp")?.Value
	</strong>
	<form method="post" action="api/Account/Logout">
		<AntiForgeryTokenInput />
		<button class="btn btn-link" type="submit">Signout</button>
	</form>
</Authorized>

The user can login using different identity providers.

The name claim and the identity provider are displayed in the UI.

Notes

Using multiple identity providers in ASP.NET Core is a simple implementation once you understand the scheme concept and the authentication flow events used. If using the one line wrappers authentication clients from the different providers, you need to override the default implementation hidden inside these libraries. Most of these packages provide extension methods and contracts for this, but sometimes it is more simple to use the standard ASP.NET Core implementation. This works for any conform OpenID Connect server.

Links

https://documentation.openiddict.com/configuration/claim-destinations.html

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

https://www.nuget.org/packages/Blazor.BFF.OpenIDConnect.Template

One comment

  1. […] Use multiple identity providers from a Blazor WASM ASP.NET Core App secured using BFF (Damien Bowden) […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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

%d bloggers like this: