Not every organization wants to hand its identity story entirely to a cloud vendor, and not every organization is running Azure AD in the first place. If you are building a product that different clients will deploy against their own identity systems, owning your own identity provider and federating out to whatever the client already runs, Keycloak, an on-prem AD FS, another vendor’s IdP, is often the more realistic architecture than picking one cloud IdP and hoping every customer standardizes on it.
This walkthrough sets up exactly that: an OpenIddict identity provider built on ASP.NET Core Identity, federating sign-in to an external Keycloak realm, while OpenIddict itself remains the single OpenID Connect server every client application in your solution actually talks to.
Why Run Your Own Identity Provider At All
The architecture here has OpenIddict as the identity provider every client application authenticates against, with ASP.NET Core Identity managing the actual account records behind it. Keycloak sits behind that as an external authentication source, not a replacement for it. Users can exist directly in ASP.NET Core Identity, or they can exist only in Keycloak, in which case direct username and password sign-in against OpenIddict can be disabled entirely, forcing everyone through the Keycloak federation path.

The real payoff of this shape is flexibility that a single hosted cloud IdP does not give you as cleanly. MFA policy, account provisioning rules, and even which identity source is authoritative can all differ per deployment, since the federation point is something you control end to end rather than a fixed vendor configuration. If you are shipping a product to multiple enterprise customers who each already run their own identity system, this pattern lets each of them federate into your app on their own terms, without you having to rebuild identity logic per customer.
Wiring Up Keycloak as an External Provider
Since Keycloak speaks standard OpenID Connect, no extra NuGet packages are required beyond what ASP.NET Core already ships, AddOpenIdConnect handles this cleanly. The one detail that is easy to get wrong and does not fail loudly when you do is SignInScheme, which needs to point at ASP.NET Core Identity’s external scheme rather than the default cookie scheme, so the incoming Keycloak identity gets treated as an external login to be linked to a local account, not as the primary sign-in session directly.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("KeyCloak", "KeyCloak", options =>
{
options.SignInScheme = "Identity.External";
// Keycloak server
options.Authority = Configuration.GetSection("Keycloak")["ServerRealm"];
// Keycloak client ID
options.ClientId = Configuration.GetSection("Keycloak")["ClientId"];
// Keycloak client secret, from user secrets or Key Vault, never in appsettings
options.ClientSecret = Configuration.GetSection("Keycloak")["ClientSecret"];
// Keycloak's well-known config endpoint
options.MetadataAddress = Configuration.GetSection("Keycloak")["Metadata"];
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.RequireHttpsMetadata = false; // dev only
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = ClaimTypes.Role,
ValidateIssuer = true
};
});
“Identity.External” is not an arbitrary string, it is the specific scheme ASP.NET Core Identity’s external login infrastructure listens on to pick up a successful external sign-in and either link it to an existing local account or prompt for account creation. Point this at the wrong scheme, or leave it as the default, and Keycloak authentication will appear to succeed while the user never actually gets signed into your application correctly.
RequireHttpsMetadata set to false is explicitly a development convenience for a local Keycloak instance running over plain HTTP. Flip this back to true, or simply remove the override, before anything resembling a production or shared environment, since disabling HTTPS metadata validation removes a real protection against a spoofed metadata endpoint.
Configuration Stays Deliberately Minimal
The appsettings entry needed for this is a standard confidential client using the authorization code flow with PKCE, nothing Keycloak-specific beyond the realm URLs.
"Keycloak": {
"ServerRealm": "http://localhost:8080/realms/myrealm",
"Metadata": "http://localhost:8080/realms/myrealm/.well-known/openid-configuration",
"ClientId": "oidc-code-pkce"
// ClientSecret is in user secrets, not committed here
}

PKCE on the authorization code flow is worth treating as non-negotiable at this point, not an optional hardening step. Any identity provider or client library that does not support PKCE cleanly on the code flow is a genuine reason to reconsider using it at all, since PKCE closes a real authorization code interception risk that older code flow implementations left open.
Why Bother When Cloud IdPs Exist
Cloud identity platforms are genuinely good at directory management, conditional access policy, and account lifecycle at scale, that is not in question. What you give up by fully outsourcing identity to one is control over the specific application-level security behaviour, forcing FIDO2 for a particular action, implementing a custom claims transformation, wiring in a bespoke MFA policy per client, that a hosted IdP’s configuration surface may not expose in the shape you need.
Running OpenIddict as your own identity provider and federating outward gives you that control back, at the cost of owning more of the identity infrastructure yourself, patching it, securing it, keeping up with OIDC spec changes. That trade is worth making when you are building a product multiple external organizations will deploy against their own identity systems. It is usually not worth making for a single internal application already living happily inside one organization’s existing Azure AD tenant, where fighting that architecture just to regain a bit of configuration control is effort better spent elsewhere.
If your product needs to support several different customer-owned identity providers rather than just Keycloak, treat the identity provider as a first-class part of your own solution architecture, not an afterthought bolted onto one client app. Federation to each customer’s IdP becomes a configuration entry against that central identity provider, rather than a separate authentication integration duplicated across every client application in your solution.
Leave a Reply