Set HttpClient Basic Auth Header per client on backchannel logout in IdentityServer4 2.5.4 (.Net Core 2.1)

147 Views Asked by At

Remark: I know that .Net Core 2.1 is old, but at this moment I cannot update. It is planned for the future.

Situation:
One client needs a Basic Authentication header to accept the Back-Channel Logout Request from OpenId Connect.

If I understand the code correctly (as seen on https://github.com/IdentityServer/IdentityServer4/blob/2.5.4/src/IdentityServer4/src/Services/Default/BackChannelLogoutHttpClient.cs), BackChannelLogoutHttpClient.PostAsync() is the function which does the actual call to the client. Which means, that the HttpClient used must get its Basic Auth Header set before the call for the relevant Url (and removed again afterwards).

Idea 1:
Write my own class which inherits from BackChannelLogoutHttpClient and with using the new keyword overwrite the original method.
Problem: I couldn't find out how to actually use the derived class. The class is used directly, not with the usual Interface in front, which would get connected to an actual class by for example using services.AddScoped<Interface, Class>();

Idea 2:
Write my own class that inherits from DefaultBackChannelLogoutService (https://github.com/IdentityServer/IdentityServer4/blob/2.5.4/src/IdentityServer4/src/Services/Default/DefaultBackChannelLogoutService.cs). This one gets called with the Interface for DI and the function that calls the other class SendLogoutNotificationAsync() is even declared virtual. Perfect. Perfect? Problem is, that setting the auth header needs access to the HttpClient, but that one is a private property of the other class. So I can't access it from here directly.

Idea 2.1:
The constructor of my Service class switches the BackChannelLogoutHttpClient out against my own implementation when calling its parent's constructor. But I couldn't get it to work. Maybe I'm missing some basic understanding here.

public class AhlcBclService: DefaultBackChannelLogoutService
{
    public AhlcBclService(
        ISystemClock clock,
        IdentityServerTools tools,
        BackChannelLogoutHttpClient backChannelLogoutHttpClient,
        ILogger<IBackChannelLogoutService> logger) : base(clock, tools, backChannelLogoutHttpClient, logger)
    {
        //What do I need to do here to call base with a fresh instance of my own class
        // (let's call it "AdditionalHeaderForLogoutClient"), 
        // where it's constructor parameters are filled with the same ones 
        // used for the original BackChannelLogoutHttpClient?
    }
}

Idea 2.2:
Use Reflection in my own SendLogoutNotificationAsync() to get around the private restriction. But I only get a null value when I try to get the property.

    protected override async Task SendLogoutNotificationAsync(BackChannelLogoutModel client)
    {
        PropertyInfo prop = typeof(BackChannelLogoutHttpClient).GetProperty("_client", BindingFlags.NonPublic | BindingFlags.Instance);
        //prop is null at this point, it doesn't seem to find the private property _client
        HttpClient cl = (HttpClient)prop.GetValue(HttpClient); //BackChannelLogoutHttpClient is saved in local variable HttpClient
        if (client.LogoutUri == "<urlWithAuth>")
            cl.SetToken("Basic", "<SomeAuthString>");
        else
            cl.DefaultRequestHeaders.Remove("Authorization");
        var data = await CreateFormPostPayloadAsync(client);
        await HttpClient.PostAsync(client.LogoutUri, data);
    }

Am I missing something obvious? Is there an intended option somewhere to extend the call to the client on a per-client basis?

The only other thing I found is using the AddBackChannelLogoutHttpClient() function with an additional option as parameter to set the auth header, but in this case it would be set for every client, not only for the intended one. And this would break down completely, when a second client with different auth values gets added to the mix.

Currently my only option is using an external relay client, which gets called from my logout with the logout_token. This then uses its own fresh HttpClient, sets the header and the received payload and then calls the actual target. Not pretty.

(If there is a way for the logout call to automatically convert a https://user:pw@url logout-url into a https://url with basic auth header set, then this would also solve my problem.)

0

There are 0 best solutions below