I am using an OpenId Connect Authentication Server, specifically Identity Server 4 (version 1.5.2) on .NET Core 1.1. I have this running with ASP.NET Framework MVC 5 and ASP.NET Core 1.1 MVC web applications. The following configuration is from a .NET Core 1.1 web application:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseRewriter(new RewriteOptions().AddRedirectToHttps());
app.UseStaticFiles();
#region Configure Authentication
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AccessDeniedPath = "/AccessDenied"
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "https://localhost:44316",
ClientId = "test-mule",
ClientSecret = "secret",
ResponseType = "code id_token",
SaveTokens = true,
GetClaimsFromUserInfoEndpoint = true,
PostLogoutRedirectUri = "https://localhost:44324",
RequireHttpsMetadata = true,
Scope = { "openid", "profile", "name", "email", "org", "role" },
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
}
});
#endregion Configure Authentication
app.UseMvc();
}
Once logged in, I can list the claims associated with the authenticated user:
var claims = User.Claims.OrderBy(c => c.Type).ToList();
On the ASP.NET 1.1 application, this delivers the following claims list:
amr pwd
aud test-mule
auth_time 1504529067
c_hash nouhsuXtd5iKT7B33zxkxg
email tom@
exp 1504532668
family_name Cobley
given_name Tom
iat 1504529068
idp local
iss https://localhost:44316
name tom
nbf 1504529068
nonce 6364012...
org IBX
role SysAdmin
role TeleMarketing
role AccountManager
role DataManager
role Member
sid 2091...
sub 1b19...440fa
Which, is what I want/expect.
I am now trying to replicate this behaviour in my first ASP.NET Core 2.0 application with the following Startup
configuration:
public Startup()
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Cookies")
.AddCookie("Cookies", options =>
{
options.LoginPath = "/SignIn";
options.AccessDeniedPath = "/AccessDenied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44316";
options.ClientId = "test-mule";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SignedOutRedirectUri = "https://localhost:44367";
options.RequireHttpsMetadata = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("name");
options.Scope.Add("email");
options.Scope.Add("org");
options.Scope.Add("role");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
// Add framework services.
services.AddMvc();
}
For reference the /Signin
controller action looks like this:
[Route("/SignIn")]
public IActionResult SignIn(string returnUrl = null)
{
if (!Url.IsLocalUrl(returnUrl)) returnUrl = "/";
var props = new AuthenticationProperties
{
RedirectUri = returnUrl
};
return Challenge(props, "oidc");
}
In this environment, after a successful login, if I list the User's claims I only see a subset of what is available in Core 1.1:
email tom@...
family_name Cobley
given_name Tom
idp local
name tom.cobley
sid 2091...
sub 1b19...440fa
I have run trace logs on both the client and the server but can't see/identify anything untoward). I am also assuming that it is not an Identity Server problem as it is 'just' an Open Id Connect service that should be consistent against any client?
Can anyone point me in the right direction as to where I am going wrong?
Thanks.
Update
Following MVCutter's suggestion I added an `OnUserInformationReceived event handler as I had noticed that not all of my custom claims were being correctly mapped to the User Identity. I am not sure why this is necessary or if there is a better place to do it, but it appears to give me what I want for now.
private Task OnUserInformationReceivedHandler(
UserInformationReceivedContext context)
{
if (!(context.Principal.Identity is ClaimsIdentity claimsId))
{
throw new Exception();
}
// Get a list of all claims attached to the UserInformationRecieved context
var ctxClaims = context.User.Children().ToList();
foreach (var ctxClaim in ctxClaims)
{
var claimType = ctxClaim.Path;
var token = ctxClaim.FirstOrDefault();
if (token == null)
{
continue;
}
var claims = new List<Claim>();
if (token.Children().Any())
{
claims.AddRange(
token.Children()
.Select(c => new Claim(claimType, c.Value<string>())));
}
else
{
claims.Add(new Claim(claimType, token.Value<string>()));
}
foreach (var claim in claims)
{
if (!claimsId.Claims.Any(
c => c.Type == claim.Type &&
c.Value == claim.Value))
{
claimsId.AddClaim(claim);
}
}
}
return Task.CompletedTask;
}
Best Answer
Please see below, I ran into the same issue as you. I'm sure there is a configuration issue we are missing, but for the time being I parsed the values coming back from the UserInfoEndpoint in the OnUserInformationReceived event handler.
Edit:
I found that there is an extension method for ClaimsAction property called MapUniqueJsonKey, this seems to work for custom single valued keys but bombs on array types like roles... getting closer