I'm still learning the Identity Framework and am pretty lost in trying to setup authentication in my .Net Core 2 MVC application. Any suggestions are appreciated since I'm not even sure what I'm doing is correct.
I have a requirement to integrate an OpenID Connect identity provider for authentication and use a secondary data source for authorization. Inconveniently I cannot use any claim from the OIDC IdP except for the name claim. The rest of the user claims must come from the secondary data source (which is connected to the Identity Framework through a custom UserStore
and User
entity).
I am using the OpenId Connect provider to handle the authentication. This is working fine and gives me the first Identity (which I can only use one Claim from). My confusion starts when I need to fetch the second Identity of the user, add it to the principal, and set it as the default Identity
. This second Identity
provides all of the user claims, including role.
My understanding of identity framework is that I should have a single ClaimsPrincipal
with two identities so that I can plug into the rest of Identity Framework. However with two identities the default ClaimsPrincipal
will automatically select the first Identity (which is the one I can't use), therefor it seems I should create a custom ClaimsPrincipal
and set the PrimaryIdentitySelector
so that my second Identity is the primary.
public class MyClaimsPrincipal : ClaimsPrincipal
{
private static readonly Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity> IdentitySelector = SelectPrimaryIdentity;
/// <summary>
/// This method iterates through the collection of ClaimsIdentities and chooses an identity as the primary.
/// </summary>
private static ClaimsIdentity SelectPrimaryIdentity(IEnumerable<ClaimsIdentity> identities)
{
// Find and return the second identity
}
}
Once I get the validated token from the OIDC IdP, I fetch the second identity, create a new MyClaimsPrincipal, add the two Identities to the new principal. After that I'm not sure what to do with this new principal.
I've tried to sign the user in via the SignInManager
, setting the User on the HTTP context explicitly, and using middleware to convert ClaimsPrincipals
to MyClaimsPrincipals
but all of these seem to do nothing. I think I am missing the point.
Some specific questions:
An important thing to know when using the OpenID Connect scheme is that the scheme will never work on its own. In pretty much every example you can find you will see it combined with the cookie scheme. The reason for this is very simple: OIDC is for authenticating the user with an external authentication provider. But that authentication is only temporary. In order to store it locally within your application, you need to sign in your user. This is usually done with the cookie authentication scheme (although it could be done in other ways).
The authentication flow for an application that uses OIDC and cookies usually works like this:
So assuming that everything worked properly, the OIDC scheme will not be involved again in the authentication. Instead, the identity from the cookie will be used every time.
You can use this for your purpose to expand the principal that the OIDC scheme created with additional claims, before it is signed in and persisted by the cookie scheme. You could do this using a custom sign-in scheme that sits between the OIDC and cookie schemes, or you could simply attach to an authentication event of the OIDC scheme that is invoked after the challenge is completed but before the sign-in occurs.
You can use the TicketReceived
event for this purpose:
public void ConfigureServices(IServiceCollection services)
{
// …
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
// …
options.Events.OnTicketReceived = OnOpenIdConnectTicketReceived;
});
}
public static Task OnOpenIdConnectTicketReceived(TicketReceivedContext context)
{
if (context.Principal.Identity is ClaimsIdentity identity)
{
identity.AddClaim(new Claim("foo", "bar"));
}
return Task.CompletedTask;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With