skip to Main Content

So as stated in the title, I am having issues with the token validation in dotnet application. Here is the program.cs code:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

// Add configuration for Ocelot
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

// Add Ocelot service
builder.Services.AddOcelot(builder.Configuration);

builder.Services.AddHttpClient();
// Keycloak configuration
builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer("KeyCloak", options =>
    {
        options.Authority = "http://localhost:5002/realms/test-realm";
        options.Audience = "api-gw";
        options.MetadataAddress = "http://keycloak:5002/realms/test-realm/.well-known/openid-configuration";
        options.RequireHttpsMetadata = false;
        
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "http://localhost:5002/realms/test-realm",
            ValidateAudience = true,
            ValidAudience = "api-gw",
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.Zero
        };
    

        options.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                Console.WriteLine("Token Validated.");
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context => 
            { 
                Console.WriteLine($"Token Validation Failed: {context.Exception.Message}");
                return Task.CompletedTask;
            }
        };
    });

// Add authorization
builder.Services.AddAuthorization();

var app = builder.Build();

// Use authentication middleware before Ocelot
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpsRedirection();

// Use Ocelot API Gateway
app.UseOcelot().Wait();

// Run the app
app.Run();

What I though would happen is that the Microsoft.AspNetCore.Authentication.JwtBearer would automatically fetch the public keys from the KeyCloak URL from MetadataAddress. Both the KeyCloak and the API Gateways are deployed as Docker containers. I have checked the connectivity, the containers can communicate. Additionally, when checking in local browser the http://localhost:5002/realms/test-realm/.well-known/openid-configuration I am receiving the proper JSON object with a link to the jwks_uri. However, I am getting the following error:

info: Ocelot.Authentication.Middleware.AuthenticationMiddleware[0]
2024-12-16 20:44:49       requestId: 0HN8U187GSHNG:00000003, previousRequestId: No PreviousRequestId, message: 'The path '/api/weatherforecast' is an authenticated route! AuthenticationMiddleware checking if client is authenticated...'
2024-12-16 20:44:50 Token Validation Failed: IDX10500: Signature validation failed. No security keys were provided to validate the signature.
2024-12-16 20:44:50 warn: Ocelot.Authentication.Middleware.AuthenticationMiddleware[0]
2024-12-16 20:44:50       requestId: 0HN8U187GSHNG:00000003, previousRequestId: No PreviousRequestId, message: 'Client has NOT been authenticated for path '/api/weatherforecast' and pipeline error set. Request for authenticated route '/api/weatherforecast' was unauthenticated;'
2024-12-16 20:44:50 warn: Ocelot.Responder.Middleware.ResponderMiddleware[0]
2024-12-16 20:44:50       requestId: 0HN8U187GSHNG:00000003, previousRequestId: No PreviousRequestId, message: 'Error Code: UnauthenticatedError Message: Request for authenticated route '/api/weatherforecast' was unauthenticated errors found in ResponderMiddleware. Setting error response for request path:/api/weatherforecast, request method: GET'

For the sake of completeness, here is the ocelot.json. The /login route goes through another small auth-service container, but I am able to receive the token just fine. The problem is with the authentication for the /weatherforecast route.

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/login",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "auth-service",
          "Port": 8080
        }
      ],
      "UpstreamPathTemplate": "/login",
      "UpstreamHttpMethod": ["Post"]
    },
    {
      "DownstreamPathTemplate": "/todos/{id}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "jsonplaceholder.typicode.com",
          "Port": 443
        }
      ],
      "UpstreamPathTemplate": "/api/todos/{id}",
      "UpstreamHttpMethod": ["Get"]
    },
    {
      "DownstreamPathTemplate": "/weatherforecast",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "temp-service",
          "Port": 8080
        }
      ],
      "UpstreamPathTemplate": "/api/weatherforecast",
      "UpstreamHttpMethod": ["Get"],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "KeyCloak",
        "AllowedScopes": []
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:5000"
  }
}

Any ideas on what could be causing the problem? From what I can deduct, the keys are not correctly loaded from the KeyCloak URL, thus the JWT library cannot validate the token without any public key.

2

Answers


  1. Chosen as BEST ANSWER

    Finally, I have managed to find the issue. I will post it here for others if such a problem arises anytime soon for somebody.

    The main problem was the mismatch of the URLs (especially the IPs) of the openid-configuration inside the KeyCloak endpoint. Beforehand I have specified the KC_HOSTNAME=localhost for KeyCloak's Docker compose deployment. While it worked for the first call as specified in the options.MetadataAddress = "http://keycloak:5002/realms/test-realm/.well-known/openid-configuration";, the jwks_uri was pointing to the localhost instead of the keycloak IP address (the KeyCloak's container name), thus the second HTTP call did not succeed. I managed to find this by deploying a distributed tracing telemetry inside the system using OpenTelemetry and Jaeger.

    So changing all values of the KC_HOSTNAME, options.Authority and the ValidIssuer to a single IP address (the containers name) keycloak fixed the error.


  2. I would try to figure out what the KID claim in the token is and what keys can be found via the JWKS endpoint and see if the key found in the tokens matches what is found in the JWKS endpoint.

    I just wrote a blog post IdentityServer in Docker Containers that some of the troubleshooting steps you can do.

    In it, I show how to add a BackChannelListener, to allow you to capture the traffic between the client and the auth service, like this:

    public class BackChannelListener : DelegatingHandler
    {
        public BackChannelListener() : base(new HttpClientHandler())
        {
        }
    
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                     CancellationToken token)
        {
            var sw = new Stopwatch();
            sw.Start();
    
            var result = await base.SendAsync(request, token);
    
            sw.Stop();
    
            Log.Logger.ForContext("SourceContext", "BackChannelListener")
                       .Information($"### BackChannel request to {request?.RequestUri?.AbsoluteUri} took {sw.ElapsedMilliseconds.ToString()} ms");
    
            return result;
        }
    }
    

    Then you can add it to JwtBearer like

    .AddJwtBearer(options =>
    {
        ...
        options.BackchannelHttpHandler = new BackChannelListener();
        options.BackchannelTimeout = TimeSpan.FromSeconds(30);
    
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search