Integrating with External Providers
Integrating with external identity providers enables your application to leverage trusted third-party authentication systems, such as social logins or corporate directories. This approach simplifies user login while ensuring secure and standardized authentication workflows.
External Identity Providers
Section titled “External Identity Providers”One option for allowing your users to login is by using an external identity provider. These external providers can be a social login for your users (e.g. Google), a corporate login system (e.g. Azure AD for employees), or some other login system your users use.
The workflow using an external provider is much like the workflow from one of your client applications using your IdentityServer. Your login page must redirect the user to the identity provider for login, and the identity provider will redirect the user to a callback endpoint in your IdentityServer to process the results. This means the external provider should implement a standard protocol (e.g. Open ID Connect, SAML2-P, or WS-Federation) to allow such an integration.
To ease integration with external providers, it is recommended to use an authentication handler for ASP.NET Core that implements the corresponding protocol used by the provider. Many are available as part of ASP.NET Core, but you might need to find others (both commercial and free) for things like SAML2-P and other social login systems not provided by ASP.NET Core.
Registering Authentication Handlers For External Providers
Section titled “Registering Authentication Handlers For External Providers”Supporting an external provider is achieved by registering the handler in your IdentityServer’s startup configuration. For example, to use employee logins from Azure AD (AAD):
builder.Services.AddIdentityServer();
builder.Services.AddAuthentication() .AddOpenIdConnect("AAD", "Employee Login", options => { // options omitted });
The above snippet registers a scheme called AAD
in the ASP.NET Core authentication system, and uses a human-friendly
display name of “Employee Login”.
The options necessary will be different based on the protocol and identity provider used, and are beyond the scope of
this documentation.
Triggering The Authentication Handler
Section titled “Triggering The Authentication Handler”To allow the user to be redirected to the external provider, there must be some code in your login page that triggers the handler. This can be done because you have provided the user with a button to click, or it could be due to inspecting some property of the authorization context, or it could be based on any other aspect of the request (e.g. such as the user entering their email).
To invoke an external authentication handler use the ChallengeAsync
extension method on the HttpContext
(or using
the MVC ChallengeResult
).
When triggering challenge, it’s common to pass some properties to indicate the callback URL where you intend to process
the external login results and any other state you need to maintain across the workflow (e.g. such as
the return URL passed to the login page):
var callbackUrl = Url.Action("MyCallback");
var props = new AuthenticationProperties{ RedirectUri = callbackUrl, Items = { { "scheme", "AAD" }, { "returnUrl", returnUrl } }};
return Challenge("AAD", props);
The Role Of Cookies In External Logins
Section titled “The Role Of Cookies In External Logins”ASP.NET Core needs a way to manage the state produced from the result of the external login. This state is managed (by default) with another cookie using ASP.NET Core’s cookie authentication handler.
This extra cookie is necessary since there are typically several redirects involved until you are done with the external authentication process.
Sign In Scheme
Section titled “Sign In Scheme”One option on external authentication handlers is called SignInScheme
.
This specifies the cookie handler to manage the state:
builder.Services.AddAuthentication() .AddOpenIdConnect("AAD", "Employee Login", options => { options.SignInScheme = "scheme of cookie handler to use";
// other options omitted });
Given that this is such a common practice, IdentityServer registers a cookie handler specifically for this external
provider workflow.
The scheme is represented via the IdentityServerConstants.ExternalCookieAuthenticationScheme
constant.
If you were to use our external cookie handler, then for the SignInScheme
above, you’d assign the value to be the
IdentityServerConstants.ExternalCookieAuthenticationScheme
constant:
builder.Services.AddAuthentication() .AddOpenIdConnect("AAD", "Employee Login", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
// other options omitted });
Alternatively, you can also register your own custom cookie handler instead. For example:
builder.Services.AddAuthentication() .AddCookie("MyTempHandler") .AddOpenIdConnect("AAD", "Employee Login", options => { options.SignInScheme = "MyTempHandler";
// other options omitted });
Sign Out Scheme
Section titled “Sign Out Scheme”SignInScheme
of the external provider should always be IdentityServerConstants.ExternalCookieAuthenticationScheme
.
The SignOutScheme
depends on whether ASP.NET Identity is used or not:
// Program.csbuilder.Services.AddAuthentication() .AddCookie("MyTempHandler") .AddOpenIdConnect("AAD", "Employee Login", options => { options.SignOutScheme = IdentityConstants.ApplicationScheme // other options omitted });
// Program.csbuilder.Services.AddAuthentication() .AddCookie("MyTempHandler") .AddOpenIdConnect("AAD", "Employee Login", options => { options.SignOutScheme = IdentityServerConstants.SignoutScheme // other options omitted });
Learn more about ASP.NET Identity and its relationship to Duende IdentityServer.
Handling The Callback
Section titled “Handling The Callback”On the callback page your typical tasks are:
- Inspect the identity returned by the external provider.
- Make a decision how you want to deal with that user. This might be different based on if this is a new user or a returning user.
- New users might need additional steps and UI before they are allowed in. Typically, this involves creating a new internal user account that is linked to the user from the external provider.
- Store the external claims that you want to keep.
- Delete the temporary cookie.
- Establish the user’s authentication session.
- Complete the login workflow.
Inspecting The External Identity
Section titled “Inspecting The External Identity”To access the result of the external login, invoke the AuthenticateAsync
method.
This will read the external cookie to retrieve the claims issued by the external provider and any other state you
previously stored when calling ChallengeAsync
:
// read external identity from the temporary cookievar result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);if (result?.Succeeded != true){ throw new Exception("External authentication error");}
// retrieve claims of the external uservar externalUser = result.Principal;if (externalUser == null){ throw new Exception("External authentication error");}
// retrieve claims of the external uservar userId = externalUser.FindFirst("sub").Value;var scheme = result.Properties.Items["scheme"];
// retrieve returnUrlvar returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// use the user information to find your user in your database, or provision a new user
The sub
claim from the external cookie is the external provider’s unique id for the user.
This value should be used to locate your local user record for the user.
Establish Session, Clean Up, And Resume Workflow
Section titled “Establish Session, Clean Up, And Resume Workflow”Once your callback page logic has identified the user based on the external identity provider, it will log the user in and complete the original login workflow:
var user = FindUserFromExternalProvider(scheme, userId);
// issue authentication cookie for userawait HttpContext.SignInAsync(new IdentityServerUser(user.SubjectId){ DisplayName = user.DisplayName, IdentityProvider = scheme});
// delete temporary cookie used during external authenticationawait HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// return back to protocol processingreturn Redirect(returnUrl);
Typically, the sub
value used to log the user in would be the user’s unique id from your local user database.
State, URL length, And ISecureDataFormat
Section titled “State, URL length, And ISecureDataFormat”When redirecting to an external provider for sign-in, frequently state from the client application must be
round-tripped.
This means that state is captured prior to leaving the client and preserved until the user has returned to the client
application.
Many protocols, including OpenID Connect, allow passing some sort of state as a parameter as part of the request, and
the identity provider will return that state in the response.
The OpenID Connect authentication handler provided by ASP.NET Core utilizes this feature of the protocol, and that is
how it implements the returnUrl
feature mentioned above.
The problem with storing state in a request parameter is that the request URL can get too large (over the common limit
of 2000 characters).
The OpenID Connect authentication handler does provide an extensibility point to store the state in your server, rather
than in the request URL.
You can implement this yourself by implementing ISecureDataFormat<AuthenticationProperties>
and configuring it on the
OpenIdConnectOptions
.
Fortunately, IdentityServer provides an implementation of this for you, backed by the IDistributedCache
implementation
registered in the ASP.NET Core service provider (e.g. the standard MemoryDistributedCache
).
To use the IdentityServer provided secure data format implementation, call the AddOidcStateDataFormatterCache
extension method on the IServiceCollection
when configuring the service provider.
If no parameters are passed, then all OpenID Connect handlers configured will use the IdentityServer provided secure data format implementation:
// configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.builder.Services.AddOidcStateDataFormatterCache();
builder.Services.AddAuthentication() .AddOpenIdConnect("demoidsrv", "IdentityServer", options => { // ... }) .AddOpenIdConnect("aad", "Azure AD", options => { // ... }) .AddOpenIdConnect("adfs", "ADFS", options => { // ... });
If only particular schemes are to be configured, then pass those schemes as parameters:
// configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache.builder.Services.AddOidcStateDataFormatterCache("aad", "demoidsrv");
See this quickstart for step-by-step instructions for adding external authentication and configuring it.