Skip to content
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

Per-Tenant Authentication with JwtBearerOptions Not Working #855

Open
DXFJaimik opened this issue Jul 11, 2024 · 4 comments
Open

Per-Tenant Authentication with JwtBearerOptions Not Working #855

DXFJaimik opened this issue Jul 11, 2024 · 4 comments
Labels

Comments

@DXFJaimik
Copy link

DXFJaimik commented Jul 11, 2024

I am using Okta IDP provider, and it works with both static implementation and multitenancy using Finbuckle. However, the Per-tenant authentication with JwtBearerOptions flow does not function correctly on Finbuckle multitenancy with per-tenant authentication.

I have followed all the provided steps, and without per-tenant authentication, the tenant is resolved properly. Tenant information is obtained based on the Host Strategy. In JwtBearerEvents, during MessageReceived, I receive all options resolved by the ConfigurePerTenant method. However, during the Challenge method, I encounter an unusual result with no error message or description. I have included a screenshot of this issue.

image

image

@DXFJaimik
Copy link
Author

DXFJaimik commented Jul 11, 2024

Added code for reference:

Program.cs:

using Finbuckle.MultiTenant;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.OpenApi.Models;
using PerTenantAuthTest;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri("https://okta-tenant-id.okta.com/oauth2/default/v1/authorize"),
                TokenUrl = new Uri("https://okta-tenant-id.okta.com/oauth2/default/v1/token"),
                Scopes = new Dictionary<string, string>
                        {
                            { "Application.FullAccess", "Access API" }
                        }
            }
        }
    });
    options.OperationFilter<AuthorizeCheckOperationFilter>();
});

builder.Services.AddMultiTenant<MyTenantInfo>()
    .WithHostStrategy("__tenant__")
    .WithConfigurationStore(builder.Configuration, "Tenant:Stores:ConfigurationStore")
.WithPerTenantAuthentication();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.ConfigurePerTenant<JwtBearerOptions, MyTenantInfo>(JwtBearerDefaults.AuthenticationScheme,
    (options, tenantInfo) =>
    {
        options.Authority = tenantInfo.Authority;
        options.Audience = "api://default";
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            RequireAudience = true,
            ValidateAudience = true,
        };
        options.Events = new MultitenantJwtBearerEvents();
    });

var app = builder.Build();

app.UseMiddleware<ExceptionHandlingMiddleware>();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.DefaultModelsExpandDepth(-1);
        options.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
        options.OAuthAppName("ApplicationName");
        options.OAuthClientId("ClientId");
        options.OAuthUsePkce();
    });
}

app.UseHttpsRedirection();

app.UseMultiTenant();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

MultitenantJwtBearerEvents.cs:

using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace PerTenantAuthTest;

public class MultitenantJwtBearerEvents : JwtBearerEvents
{
    public override Task AuthenticationFailed(AuthenticationFailedContext context)
    {
        return base.AuthenticationFailed(context);
    }
    public override Task MessageReceived(MessageReceivedContext context)
    {
        return base.MessageReceived(context);
    }
    public override async Task TokenValidated(TokenValidatedContext context)
    {        
        await base.TokenValidated(context);
    }

    public override Task Challenge(JwtBearerChallengeContext context)
    {
        context.HandleResponse();
        if (!context.Response.HasStarted)
        {
            throw new UnauthorizedAccessException($"{context.Error!} {context.ErrorDescription!}");
        }

        return Task.CompletedTask;
    }

    public override Task Forbidden(ForbiddenContext context)
    {
        throw new Exception();
    }
}

@AndrewTriesToCode
Copy link
Contributor

Hi, this looks like a tough one. Just to check, do you mean to say "pre" tenant authentication or "per" tenant authentication? And the issue is only on validating the JWT token AFTER the whole oauth2 workflow has completed?

One quick thing to try, place your AddAuthentication line before the AddMultiTenant in your setup-- MultiTenant overrides some of the authentication related DI which might be why you are seeing this.

@DXFJaimik
Copy link
Author

Thank you for your quick reply. This is per tenant authentication, and there is an issue with JWT token validation.

I tried your suggestion to place the AddAuthentication line before the AddMultiTenant, but it is returning the same result.

@DXFJaimik DXFJaimik changed the title Pre-Tenant Authentication with JwtBearerOptions Not Working Per-Tenant Authentication with JwtBearerOptions Not Working Jul 13, 2024
@AndrewTriesToCode
Copy link
Contributor

So looking at this again, in the jwt scheme handler the challenge method doesn’t really do much before calling the event handler. If you tell it you handled the event (via your call to context HandledResponse) it will not generate the 401 challenge response because it assumes you have done so. Is that what you intended?

In your top screenshot I can’t tell if the options passed to the challenge event were resolved correctly for the tenant. can you include that part in the screenshot?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants