.net core 2.2 multiple authentication schemes

Viewed 1180

I'm trying to make a .net core web api application with authentication. I need to implement windows authentication, cookie authentication, ldap active directory authentication and jwt bearer authentication. Every authentication work fine alone but if I try to enable all together s it does't work as I expect. To make work windows authentication I've write a custom ForwardDefaultSelector as follows:

services.AddAuthentication(optons =>
           {
               optons.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultAuthenticateScheme = "smart";
               optons.DefaultChallengeScheme = "smart";
               optons.DefaultScheme = "smart";
               //optons.AddScheme("Windows", config => config.HandlerType = IISDefaults.)
           })
           .AddPolicyScheme("smart", "", options =>
           {
               options.ForwardDefaultSelector = context =>
               {
                   var authHeader = context.Request.Headers[HeaderNames.Authorization].FirstOrDefault();
                   if (!string.IsNullOrEmpty(authHeader))
                   {
                       if (authHeader.StartsWith(JwtBearerDefaults.AuthenticationScheme) == true)
                           return JwtBearerDefaults.AuthenticationScheme;
                       else if (authHeader.StartsWith(IISDefaults.Negotiate) == true || authHeader.StartsWith(IISDefaults.Ntlm) == true)
                           return IISDefaults.AuthenticationScheme;

                   }
                   else
                   {
                       if (context.Request.Cookies.ContainsKey(string.IsNullOrEmpty(Configuration["ApplicationName"]) ? ".Authentication.Aion" : ".Authentication." + Configuration["ApplicationName"]))
                       {
                           return CookieAuthenticationDefaults.AuthenticationScheme;
                       }
                       else if (context.Request.Cookies.ContainsKey("LDAP_AUTH"))
                       {
                           return LdapAuthenticationDefaults.AuthenticationScheme;
                       }
                       else
                       {
                           return IISDefaults.AuthenticationScheme;
                       }
                   }
                   return CookieAuthenticationDefaults.AuthenticationScheme;

               };
           })
           .AddCookie(options =>
           {
               options.Cookie.Name = string.IsNullOrEmpty(Configuration["ApplicationName"]) ? ".Authentication.Aion" : ".Authentication." + Configuration["ApplicationName"];
               //options.Cookie.HttpOnly = true;
               options.Cookie.SecurePolicy = CookieSecurePolicy.None;
               options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
               options.SlidingExpiration = true;
               options.LoginPath = new PathString("/Account/Login");
               options.LogoutPath = new PathString("/Account/Logout");
               options.AccessDeniedPath = new PathString("/Account/Authorize");
               var expirationDays = Configuration.GetValue<int>("DefaultExpirationDays");
               options.ExpireTimeSpan = TimeSpan.FromDays(expirationDays);
               options.Events.OnValidatePrincipal = async (context) =>
               {
                   await SecurityStampValidator.ValidatePrincipalAsync(context);
               };
               //options.Events.OnSignedIn = context =>
               //{
               //    return Task.CompletedTask;
               //};
               //options.ForwardAuthenticate = "Ldap";

           })
           .AddJwtBearer(options =>
           {
               options.RequireHttpsMetadata = false;
               //options.SaveToken
               var session = Configuration.GetSection("BearerAuthenticationConfigs");

               options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
               {
                   ValidateIssuer = false,
                   ValidateAudience = false,
                   ValidateLifetime = true,
                   ValidateIssuerSigningKey = true,
                   RequireSignedTokens = false,
                   //ValidAudience = "http://localhost:5000",
                   //ValidIssuer = "http://localhost:5000",
                   RequireExpirationTime = false,
                   IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(session.GetValue<string>("JwtSecret"))),
                   ClockSkew = TimeSpan.Zero
               };
               options.Authority = "http://localhost:5000";
               options.Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
               {
                   TokenEndpoint = new PathString("/Token"),
                   AuthorizationEndpoint = new PathString("/Account/Authorize")

               };
               options.EventsType = typeof(JWTValidationEvents);
           })
           .AddScheme<LdapAuthenticationSchemeOptions, LdapAuthenticationHandler>(LdapAuthenticationDefaults.AuthenticationScheme, options =>
           {
               var configSession = Configuration.GetSection("LdapAuthenticationConfigs");

               options.LdapHost = configSession.GetValue<string>("Host", "localhost");
               options.LdapPort = configSession.GetValue<int>("Port", 389);
               var expireDays = configSession.GetValue<int?>("ExpirationDays", Configuration.GetValue<int>("DefaultExpirationDays"));
               options.Expire = expireDays.HasValue ? TimeSpan.FromDays(expireDays.Value) : (TimeSpan?)null;
               options.UserDomain = configSession.GetValue<string>("UserDomain", "");
           });

and web.config to enable windows auth on IIS is follow

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <!-- To customize the asp.net core module uncomment and edit the following section. 
  For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->

  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <security>
        <authentication>
         <!--I need to enable anonymous authentication for token call-->
          <anonymousAuthentication enabled="true" />
          <windowsAuthentication enabled="true" />
        </authentication>
      </security>
    </system.webServer>
  </location>

</configuration>

While the custom Middleware to manage windows auth is:

public class WindowsLoginMiddleware
    {
        private readonly RequestDelegate _next;
        //private readonly ILogger _logger;
        private readonly ILogger<WindowsLoginMiddleware> _logger;




        public WindowsLoginMiddleware(RequestDelegate next, ILogger<WindowsLoginMiddleware> Logger, ApplicationDbContext db, IHttpContextAccessor httpcontextaccessor)
        {
            _next = next;
            _logger = Logger;
            _db = db;
            _httpContextAccessor = httpcontextaccessor;
        }

        public async Task InvokeAsync(HttpContext context, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
        {
            if (signInManager.IsSignedIn(context.User))
            {
                _logger.LogInformation("User already signed in");
            }
            else
            {
                if (context.User.Identity as WindowsIdentity != null)
                {
                    _logger.LogInformation($"User with Windows Login {context.User.Identity.Name} needs to sign in");
                    var windowsUsername = context.User.Identity.Name;
                    //Here I create principal claim from db application user and save on cookie for next requests
                    var identity = await WindowsAuthenticationClaimProvider.GetIdentityClaimsAsync(windowsUsername, string.Empty, true);
                    if(identity == null)
                    {
                        _logger.LogInformation($"User {context.User.Identity.Name} has no role in system");
                        context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                        await context.Response.WriteAsync("");
                        return;
                    }
                    else
                    {
                        _logger.LogDebug($"User with username {windowsUsername} successfully signed in");
                    }
                }
            }

            // Pass the request to the next middleware
            await _next(context);
        }
    }

It work, but I don't like the custom ForwardDefaultSelector because all request have default IISDefaults.AuthenticationScheme, and windows authentication make 2 call to get WindowsIdentity (pass 2 times on WindowsLoginMiddleware).

Have someone a better solution to manage this situation? Thanks.

0 Answers
Related