Quantcast
Channel: ASP.NET Core – Software Engineering
Viewing all articles
Browse latest Browse all 169

Using multiple external identity providers from ASP.NET Core Identity and Duende IdentityServer

$
0
0

This blog post shows how an ASP.NET Core Identity application can integrate and implement multiple external identity providers. An OIDC client UI uses the solution and is implemented using Duende IdentityServer. The same scheme is used for all the external providers and mapped to the identity for the client UI and the application. Using OpenID Connect this is returned to the web application in tokens or the user profile API.

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

Setup

The application is used as an identity provider. This can be used for local users or for external users using OpenID Connect federation. All applications using the application are separated from the further authentication systems. By using Duende, it is possible to use the high end OAuth an OpenID Connect authentication flows which are not supported by some of the other well known identity providers. It would also be possible to use OpenIddict in this setup. The users of the server authenticate using OpenID Connect. The claims need to be mapped as well as each of the external authentication providers. The Identity Callback UI is used to handle all of the external authentication flow results. The claims from each external authentication are different and need to be mapped to the claims used in the closed system.

External providers

When implementing external authentication providers in ASP.NET Core Identity, different strategies can be used. Each external provider uses a separate scheme for the OpenID Connect flow. On a successful result , the identity can be persisted to a common external identity session or each one can use a unique scheme. Both have advantages and disadvantages. If all use the same, the logout and callback scheme logic can be simple and the claims mapping are implemented on a per provider logic. If separate schemes are used for each provider, the callback and the logout require scheme logic and checks.

In this demo, we follow the recommendation from the Duende samples and use one scheme to persist the session for all external providers. Each external provider MUST use specific URLs for the authentication flow, otherwise the state and the flows will not work as the different providers break.

builder.Services.AddAuthentication(options =>
{
   options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
   options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
   options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddOpenIdConnect("Auth0Scheme", "Auth0", options =>
{
   // SignInScheme must match the scheme(s) used in the Identity callback
   options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
   options.SignOutScheme = IdentityConstants.ApplicationScheme;
   
   // paths must be different for each client
   options.CallbackPath = new PathString("/signin-oidc-auth0");
   options.RemoteSignOutPath = new PathString("/signout-callback-oidc-auth0");
   options.SignedOutCallbackPath = new PathString("/signout-oidc-auth0");

   // more oidc options ...
   };
})
.AddOpenIdConnect("EntraID", "EntraID", oidcOptions =>
{
   builder.Configuration.Bind("AzureAd", oidcOptions);
   oidcOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
   oidcOptions.SignOutScheme = IdentityConstants.ApplicationScheme;

   oidcOptions.CallbackPath = new PathString("/signin-oidc-entraid");
   oidcOptions.RemoteSignOutPath = new PathString("/signout-callback-oidc-entraid");
   oidcOptions.SignedOutCallbackPath = new PathString("/signout-oidc-entraid");

   // more oidc options ...
});

Using Microsoft.Identity.Web

If using the Microsoft.Identity.Web Nuget packages to implement the external provider logic, a new separate scheme is required for the handling of the callback and logout because the AddMicrosoftIdentityWebApp extension method creates it’s own scheme and cannot re-use the default scheme defined by Identity. The scheme would then require implementation logic in the callback UI and the logout logic.

Duende IProfileService

If using ASP.NET Core Identity together with an OpenID Connect provider like Duende IdentityServer or OpenIddict, the claims from the different external providers need to be mapped back to the claims used by the different UI applications. In Duende, the claims can be mapped using the IProfileService. See the Duende documentation for this. The GetProfileDataAsync is can be called multiple times for each successful authentication of a UI application, each time for a different claims type. What is used depends on the OpenID Connect client setup. You should avoid adding claims multiple times for the same value and avoid added too many claims to the identity token. The mapping should work in the same way for identity token mapping or when the client uses the user info endpoint.

When using many client applications, you should aim for standard claims and not use different claims depending on the multiple downstream external authentication providers.

public class ProfileService: IProfileService
{
	public async Task GetProfileDataAsync(ProfileDataRequestContext context)
	{
        // context.Subject is the user for whom the result is being made
        // context.Subject.Claims is the claims collection from the user's session cookie at login time
        // context.IssuedClaims is the collection of claims that your logic has decided to return in the response

        if (context.Caller == IdentityServerConstants.ProfileDataCallers.ClaimsProviderAccessToken)
        {
            // access_token
        }
        if (context.Caller == IdentityServerConstants.ProfileDataCallers.ClaimsProviderIdentityToken)
        {
            // id_token
            var oid = context.Subject.Claims.FirstOrDefault(t => t.Type == "oid");
            if(oid != null)
            {
                context.IssuedClaims.Add(new Claim("oid", oid.Value));
            }
        }
        if (context.Caller == IdentityServerConstants.ProfileDataCallers.UserInfoEndpoint)
        {
            // user_info endpoint 
        }

        // ALL
        context.IssuedClaims.Add(new Claim("test", "A"));
        return;
	}

Mapping claims in Identity only solutions

If not using an OIDC server and only using ASP.NET Core Identity, a ClaimsTransformation can be implemented to map the claims.

Links

https://docs.duendesoftware.com/identityserver/reference/services/profile-service

https://duendesoftware.com/products/identityserver

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

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

https://github.com/damienbod/MulitipleClientClaimsMapping

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


Viewing all articles
Browse latest Browse all 169

Trending Articles