ASP.NET Core - adding/removing authentication schemes to/from AuthorizationPolicy at runtime

59 Views Asked by At

My ASP.NET Core 8 App uses multiple authentication schemes that I need to be able to turn on/off at runtime. Adding/removing authentication schemes to/from the IAuthenticationSchemeProvider at runtime works fine - I've used this sample at a starting point. However, I need my default authorization to contain multiple schemes since I'm using multiple schemes for Jwt (local auth in my app, and ADFS). So, my ConfigureServices(IServicecollection services) contains this:

    var authPolicyBuilder = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
        .AddAuthenticationSchemes(Contants.MyCustomAuthScheme);
    
    services.AddAuthorizationBuilder()
        .SetDefaultPolicy(authPolicyBuilder.Build());

Now, Constants.MyCustomAuthScheme may not be enabled by default, so the second call to AddAuthenticationSchemes would be commented out. And now I need to be able to add this auth scheme at runtime. I found I can access my AuthorizationPolicy at runtime from IAuthorizationPolicyProvider.GetDefaultPolicy, and while AuthorizationPolicy exposes the AuthenticationSchemes property, it's read-only. I figure for enabling the additional scheme, I might create another AuthorizationPolicy when needed and using AuthorizationPolicy.Combine to combine my default authorization policy with another that is the same except for the scheme. But, what if I want to disable a scheme at runtime?

1

There are 1 best solutions below

0
Stephan Steiner On

So, I finally found a solution. Combining AuthorizationPolicy was no options as you cannot modify/replace AuthorizationPolicies returned by the IAuthorizationPolicyProvider. So I had to go all in, writing my own implementation of IAuthorizationPolicyProvider instead. I basically copied the DefaultAuthorizationPolicyBuilder and added two methods to it:

public async Task EnableScheme(string scheme)
{
    var defaultPolicy = await GetDefaultPolicyAsync().ConfigureAwait(false);
    if (!defaultPolicy.AuthenticationSchemes.Contains(scheme))
    {
        List<string> newSchemes = [.. defaultPolicy.AuthenticationSchemes];
        newSchemes.Add(scheme);
        var newPolicy = new AuthorizationPolicy(defaultPolicy.Requirements, newSchemes);
        _options.DefaultPolicy = newPolicy;
    }
}

public async Task DisableScheme(string scheme)
{
    var defaultPolicy = await GetDefaultPolicyAsync().ConfigureAwait(false);
    if (defaultPolicy.AuthenticationSchemes.Contains(scheme))
    {
        List<string> newSchemes = [.. defaultPolicy.AuthenticationSchemes];
        newSchemes.Remove(scheme);
        var newPolicy = new AuthorizationPolicy(defaultPolicy.Requirements, newSchemes);
        _options.DefaultPolicy = newPolicy;
    }
}

I'm only using the default policy, you could easily do it for the backup policy, too. If you need it for all policies, then you're out of luck since AuhthorizationOptions don't expose the PolicyMap.