While creating JWT for a user in my authentication service I use System.Security.Claims.ClaimTypes static class with defined string constants for various claims. So ClaimTypes.Role == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role":
var claims = new List<Claim>
{
new Claim("ID", user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username)
};
claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role)));
Then, when for some ReRoute in RouteClaimsRequirement I write:
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" : "Admin"
"RouteClaimsRequirement": {
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
}
such ReRoute just disappears somewhere in the guts of middleware (I didn't manage to track down where this happens) and a request results in 404 because a reroute is not found:
Error Code: UnableToFindDownstreamRouteError Message: Unable to find downstream route for path: /api/entities/, verb: POST errors found in ResponderMiddleware. Setting error response for request path:/api/entities/, request method: POST
When I use my own claim type like "Role", this works fine. So I assume there are some issues with serialization/deserialization of a string containing colons or slashes, basically as any url.
I'm having this problem as well and was about to open a new issue before I found this one. I am using ClaimsIdentity.DefaultRoleClaimType.
claims.AddRange((await this.userManager.GetRolesAsync(user)).Select(o => new Claim(ClaimsIdentity.DefaultRoleClaimType, o)));
I have verified that my claim is working as intended by inspecting my token with https://jwt.io/.
Commenting to bring attention to this issue.
After doing a lot of digging in the Ocelot code it looks like this isn't caused by Ocelot, but is a limitation in how Microsoft parses json files.
I also found this relevant issue on Stackoverflow: https://stackoverflow.com/questions/50788190/is-there-a-way-to-escape-colon-in-appsetting-json-dictionary-key-in-aspnetcore-c
Our authentication system has colons in all claim keys due to its naming scheme, so it looks like we can't use claims with Ocelot.
If we configure RouteClaimsRequirement like this:
"RouteClaimsRequirement": {
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
}
The real result looks like:
"RouteClaimsRequirement": {
"http": {
"//schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
}
}
That why claims checking is failed. So my solution is do not use colons. Here is my explain:
ClaimsAuthoriser. In the method Authorise, we have to preprocess routeClaimsRequirement before pass it to real authorizer. For safety, I suggest to clone it and transforming on the copy.IClaimsAuthoriser is registered in container./ to replace :public class ClaimAuthorizerDecorator : IClaimsAuthoriser
{
private readonly ClaimsAuthoriser _authoriser;
public ClaimAuthorizerDecorator(ClaimsAuthoriser authoriser)
{
_authoriser = authoriser;
}
public Response<bool> Authorise(ClaimsPrincipal claimsPrincipal,
Dictionary<string, string> routeClaimsRequirement,
List<PlaceholderNameAndValue> urlPathPlaceholderNameAndValues)
{
var newRouteClaimsRequirement = new Dictionary<string, string>();
foreach (var kvp in routeClaimsRequirement)
{
if (kvp.Key.StartsWith("http///"))
{
var key = kvp.Key.Replace("http///", "http://");
newRouteClaimsRequirement.Add(key, kvp.Value);
}
else
{
newRouteClaimsRequirement.Add(kvp.Key, kvp.Value);
}
}
return _authoriser.Authorise(claimsPrincipal, newRouteClaimsRequirement, urlPathPlaceholderNameAndValues);
}
}
IClaimsAuthoriser servicepublic static class ServiceCollectionExtensions
{
public static IServiceCollection DecorateClaimAuthoriser(this IServiceCollection services)
{
var serviceDescriptor = services.First(x => x.ServiceType == typeof(IClaimsAuthoriser));
services.Remove(serviceDescriptor);
var newServiceDescriptor = new ServiceDescriptor(serviceDescriptor.ImplementationType, serviceDescriptor.ImplementationType, serviceDescriptor.Lifetime);
services.Add(newServiceDescriptor);
services.AddTransient<IClaimsAuthoriser, ClaimAuthorizerDecorator>();
return services;
}
}
// then add it to service
services.AddOcelot();
services.DecorateClaimAuthoriser(); // put it right after registering Ocelot
"RouteClaimsRequirement": {
"http///schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
}
Ocelot.16.0.1
Big thanks @tmvan I started on your path and find your solution midway. Great approach and thanks for your sharing !
Most helpful comment
If we configure
RouteClaimsRequirementlike this:The real result looks like:
That why claims checking is failed. So my solution is do not use colons. Here is my explain:
ClaimsAuthoriser. In the methodAuthorise, we have to preprocessrouteClaimsRequirementbefore pass it to real authorizer. For safety, I suggest to clone it and transforming on the copy.IClaimsAuthoriseris registered in container.Here is pseudocode that I use
/to replace:Decorator
Decorate
IClaimsAuthoriserserviceUpdate ocelot config