Search code examples
c#authorizationidentityserver4idp

Chose identity scheme based on url parameter


I am trying to choose openId authorization scheme based on request parameter.

I added two openIdConnect schemas:

.AddOpenIdConnect("abc", abcHandler)
.AddOpenIdConnect("def", defHandler);

At the beginning I overrode the whole authorisation middleware and challenging:

if (authorizeResult.Challenged)
{
    if (valueFromQueryString.Contains("abc"))
    {
        await context.ChallengeAsync("abc");
    }
    else if (valueFromQueryString.Contains("def"))
    {
        await context.ChallengeAsync("def");
    }
}

But then I noticed there are some policies, so tried to use those. I created my requirement and then added two policies with schemas like this:

services.AddAuthorization(options =>
    {
        options.AddPolicy("abc", policy =>
            {
                policy.AuthenticationSchemes.Add("abc");
                policy.Requirements.Add(new TenantSpecificRequirement("abc"));
                policy.RequireAuthenticatedUser();
             });
        options.AddPolicy("def", policy =>
            {
                policy.AuthenticationSchemes.Add("def");
                policy.Requirements.Add(new TenantSpecificRequirement("def"));
                policy.RequireAuthenticatedUser();
            });
    });

and I realized that the policy has to be bound to controller like this :

[Authorize(Policy = "abc")]
[Authorize(Policy = "def")]

which is totally not what I need. The best now would be to assign the policy to the controller dynamically based on url, but it seems it is not possible since controllers filters aren't added when then application starts.

So is there any other way?


Solution

  • Ok I found the solution by overriding the AuthenticationSchemeProvider

    public class SubdomainAuthenticationSchemeProvider : AuthenticationSchemeProvider
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
    
        public CustomAuthenticationSchemeProvider(
            IHttpContextAccessor httpContextAccessor,
            IOptions<AuthenticationOptions> options)
            : base(options)
        {
            this._httpContextAccessor = httpContextAccessor;
        }
    
        private Task<AuthenticationScheme> GetRequestSchemeAsync()
        {
            // Get the subdomain for the host the request came from. Assumes no WWW in the string.
            var subdomainName = _httpContextAccessor.HttpContext.Request.Host.Host.Split(new char[] { '.' })[0];
    
            return GetSchemeAsync(subdomainName);
        }
    
        public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync() => await GetRequestSchemeAsync() ;
    
        public override async Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultChallengeSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultForbidSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultForbidSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultSignInSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultSignInSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultSignOutSchemeAsync();
    
    }
    

    After this, add it to the ConfigureServices method

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddSingleton<IAuthenticationSchemeProvider, SubdomainAuthenticationSchemeProvider>();