Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolving 'IsAuthenticated' False Issue with Ocelot API Gateway and OKTA Authentication #2055

Closed
utpal-appseconnect opened this issue Apr 29, 2024 · 0 comments

Comments

@utpal-appseconnect
Copy link

I've been working on setting up OKTA authentication with Ocelot API Gateway and a .NET Core microservice. I followed the recommended approach of configuring the OKTA authentication middleware and related settings only in the Ocelot project, while keeping the microservice free of any authentication-specific configurations.
However, even after correctly configuring Ocelot and forwarding the Authorization header to the downstream microservice, I'm still encountering an issue where the IsAuthenticated property is false in the microservice's controller.
Here's a summary of the steps I've taken:

Ocelot API Gateway Project (Program.cs):

Bound OKTA settings from the configuration.
Configured the OKTA authentication middleware and set it as the default authentication scheme.
Configured JWT Bearer authentication with OKTA settings (authority, audience, token validation parameters).
Added UseAuthentication and UseAuthorization middleware to the HTTP request pipeline.

Here is the code

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Values;
using Okta.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var oktaSettings = new OktaWebApiOptions();
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
var serviceProvider = serviceCollection.BuildServiceProvider();
 var _logger = serviceProvider.GetService<ILogger<Program>>();
//var _logger = app.Services.GetRequiredService<ILogger<Program>>();
builder.Configuration.GetSection("Okta").Bind(oktaSettings);
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
    options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
    options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
})
    .AddJwtBearer("Okta", options =>
    {
        options.Authority = $"{oktaSettings.OktaDomain}/oauth2/{oktaSettings.AuthorizationServerId}";
        options.Audience = oktaSettings.Audience;
        options.RequireHttpsMetadata = false;
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                var exception = context.Exception.Message;
                _logger.LogError("Authentication failed: {ErrorMessage}", context.Exception.Message);
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            {
                var name = context.Principal.Identity.Name;
                _logger.LogInformation("Token validated for {UserName}", context.Principal.Identity.Name);
                return Task.CompletedTask;
            },
            OnChallenge = context =>
            {
                if (context.AuthenticateFailure != null)
                {
                    var errorMessage = context.AuthenticateFailure.Message;
                    _logger.LogError("Authentication challenge failed: {ErrorMessage}", context.AuthenticateFailure.Message);
                }
                return Task.CompletedTask;
            }
        };
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            NameClaimType = "sub",  // Setting the NameClaim to sub as that's a common identifier
            RoleClaimType = "roles"  // Adjust if your token includes a different claim type for roles
        };
    });
// Scope policy to enforce scope-based authorization
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AccessAll", policy => policy.RequireClaim("scp", "AccessAll"));
});
builder.Configuration.AddJsonFile("ocelot.json");
builder.Services.AddOcelot(builder.Configuration);
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", builder =>
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader());
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowAll");


app.MapControllers();

// Ocelot middleware
app.UseOcelot().Wait();

app.Run();

Ocelot ocelot.json Configuration:

Defined routes with AuthenticationProviderKey set to "Okta".
Included HttpHandlerOptions to forward the Authorization header.

Ocelot.json

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/WeatherForecast",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5157
        }
      ],
      "UpstreamPathTemplate": "/WeatherForecast",
      "UpstreamHttpMethod": [ "Get" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Okta",
        "AllowedScopes": []
      }
    },
    {
      "DownstreamPathTemplate": "/WeatherForecast/whoami",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5157
        }
      ],
      "UpstreamPathTemplate": "/WeatherForecast/whoami",
      "UpstreamHttpMethod": [ "Get" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Okta",
        "AllowedScopes": []
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:5021"
  },
  "HttpHandlerOptions": {
    "AllowAutoRedirect": false,
    "UseTrailingSlashesIfNecessary": false,
    "TransferEncodingChunked": false,
    "UseSystemPolicies": true,
    "HandleSynchronousIO": true,
    "UseCookieHeader": true,
    "UseAuthCacheHeader": true,
    "UseAuthorizationHeaderForCacheControl": true,
    "UseProxyHeaders": true
  }
}

Microservice API (Program.cs):

No authentication-specific configurations (as per the recommended approach).

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

Microservice API Controller:

Inspected the HttpContext.User.Identity and checked the IsAuthenticated property.

[HttpGet]
[Route("whoami")]
public Dictionary<string, string> GetAuthorized()
{
    var authHeader = HttpContext.Request.Headers["Authorization"].FirstOrDefault();
    _logger.LogInformation("Authorization Header: {AuthHeader}", authHeader);
   
    var principal = HttpContext.User.Identity as ClaimsIdentity;
    return principal.Claims
       .GroupBy(claim => claim.Type)
       .ToDictionary(claim => claim.Key, claim => claim.First().Value);
}

Despite following the recommended approach, the IsAuthenticated property is still false in the microservice's controller, indicating that the authentication is not working as expected.
I've tried several troubleshooting steps, including:

Verifying the Ocelot configuration and routes.
Ensuring the Authorization header is being forwarded correctly.
Enabling detailed logging in both Ocelot and the microservice.
Inspecting the incoming request headers and JWT token in the microservice.

However, I haven't been able to identify the root cause of the issue.
I would appreciate if anyone has faced a similar issue or has any insights into resolving this problem. Any help or guidance would be greatly appreciated.
Additionally, if you have any alternative approaches or recommendations for handling authentication with Ocelot API Gateway and microservices, please share them as well.

Thanks
Utpal Maity

@ThreeMammals ThreeMammals locked and limited conversation to collaborators Apr 29, 2024
@raman-m raman-m converted this issue into discussion #2056 Apr 29, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant