question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Ocelot doesn't handle correctly RouteClaimsRequirement with a key as an Url

See original GitHub issue

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.

Specifications

  • Version: 12.0.1

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:8
  • Comments:7

github_iconTop GitHub Comments

7reactions
vantmcommented, Jul 23, 2020

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:

  1. Create a decorator of 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.
  2. Decorate the IClaimsAuthoriser is registered in container.
  3. Edit in configuration, replace colon to your own define character.

Here is pseudocode that I use / to replace :

Decorator
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);
    }
}
Decorate IClaimsAuthoriser service
public 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
Update ocelot config
"RouteClaimsRequirement": {
  "http///schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin"
}

Ocelot.16.0.1

1reaction
tugrulelmascommented, Aug 27, 2021

I have a similar issue. Even if I don’t use ClaimTypes.Role and just try to add claims with the type role as string, Ocelot can’t find the role in the user’s claims and returns 403 forbidden response.

After debugging, I found out that the problem is not about Ocelot. The problem occurs becasue of the behavior of System.IdentityModel.Tokens.Jwt. When the ClaimsIdentity is creating, claim types is checked and if it’s a well-known type then it is replaced with it’s URI. You can find it here. So if I create a token that has a claim with the type role, HttpContext.User will has a claim with the type http://schemas.microsoft.com/ws/2008/06/identity/claims/role.

In order to change this behavior and stop the type replacing I added following line in ConfigureServices on my Startup.cs. JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

After that, I create JWT token with the following ClaimsIdentity;

new ClaimsIdentity(new Claim[] {
     new Claim("role", "admin"),
});

Then, I can check this claim in ocelot.json as following;

"RouteClaimsRequirement": {
  "role": "admin"
}

Clearing DefaultInboundClaimTypeMap may create some bugs but I’m not sure that. The first thing that I noticed is IsInRole method returns always false because it checks the claims with the URI http://schemas.microsoft.com/ws/2008/06/identity/claims/role

Read more comments on GitHub >

github_iconTop Results From Across the Web

Ocelot RouteClaimsRequirement does not recognize my ...
How the problem is handled: We are writing the url with special symbol instead of : and after that we rewrite it to...
Read more >
[Fix]-Ocelot RouteClaimsRequirement does not recognize my ...
How the problem is handled: We are writing the url with special symbol instead of : and after that we rewrite it to...
Read more >
Secure Microservices Using JWT With Ocelot in .NET Core
In this article, we are going to explore how we secure our microservices behind the Ocelot API Gateway by using JWT authentication.
Read more >
Ocelot Documentation
Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified.
Read more >
Designing and implementing API Gateways with Ocelot in . ...
The API Gateway offers a reverse proxy to re-direct or route requests (layer 7 routing, usually Http requests) to the endpoints of the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found