Reputation: 1923
I am having some issues after making some tweaks to an IdentityServer4 Quickstart sample solution, specifically the 8_AspNetIdentity sample.
I'll preface this by saying I'm not sure if what I'm trying to do is just not supported, or if I'm doing it wrong.
This sample solution contains the following projects relevant to my question:
What I am trying to do is merge the API project into the MVCClient, so that the MVCClient could both authenticate the users from its MVC website with OIDC, and also the ResourceOwnerClient using bearer authentication.
I made the following changes to the MVCClient's Startup.cs:
changed services.AddMvc();
to:
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder(new[]
{
JwtBearerDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
"oidc"
})
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
added JWT bearer options to the services.AddAuthentication()
:
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
})
Now technically this did work, as both the ResourceOwnerClient and the MVC users can successfully authenticate with the MVCClient. I however have one caveat:
When I authenticate with a user from the MVC side, I noticed that there are two identities in my current User. Both are identical in terms of claims, etc. This only happens when I put a breakpoint in the MVCClient, on the IdentityServer there is only one identity.
On the IdentityServer, I have registered a UserClaimsPrincipalFactory which adds my own custom claims to the ClaimsIdentity. In the two identities on the IdentityServer, I can see the claims duplicated. So instead of having one identity with two custom claims, I see two identities which each have 4 custom claims. The CreateAsync method in my UserClaimsPrincipalFactory is also getting hit 5 times for a single login.
Although this behaviour is strange, it does not seem to be having any negative impacts. But this is only a proof of concept for a larger application that I'm building, and I'm afraid I may run into issues in the future because of it.
If anyone has attempted this sort of thing before, or knows why this behaviour could be happening, any help would be appreciated.
Upvotes: 2
Views: 221
Reputation: 32088
While nothing bad should happen with this design, I would completely remake it. Why? Because you are mixing a Client
and an ApiResource
, and they should be logically separated. A Client
is an application, something some user interacts with, even if it was a headless one (i.e an automated service); while an ApiResource
consists of resources that are provided to Clients, so no user can interact with it directly.
You could add two authentications against IdentityServer, one as API (and add it as JwtBearer
) and one as a Client (and add it as Cookies
). You can then use [Authorize(AuthenticationSchemes = "JwtBearer")]
and = "Cookies"
depending on the function of that Action/Controller.
Leaving that aside, the problem is that your application is getting one Identity for the MVC side and one for the API side, since it has no way of telling which one you want.
Just so you have an idea, this is how one of my IdentityServers with ASP.NET Core Identtiy look like, where you can login against it using the UI and also hit the REST endpoints with a JwtToken:
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["IdentityServerUrl"];
options.ApiName = Configuration["ApiName"];
options.RequireHttpsMetadata = false;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
});
Upvotes: 3