title: How do I set up basic authentication on specific actions in my asp net core 3.1 controller?
body: <p>I am creating a webapi using asp.net core 3.1 and I want to use basic authentication, which I will authenticate against Active Directory.</p>
  <p>I create an authentication handler and service, but the problem is that when I decorate my controller action with [Authorize], the HandleAuthenticateAsync function is not called when I invoke the controller action (although the hander's constructor is called).  Instead I just get a 401 response:</p>
  <pre><code>GET https://localhost:44321/Test/RequiresAuthentication HTTP/1.1
  Authorization: Basic ..........
  User-Agent: PostmanRuntime/7.26.8
  Accept: */*
  Postman-Token: d58490e4-2707-4b75-9cfa-679509951860
  Host: localhost:44321
  Accept-Encoding: gzip, deflate, br
  Connection: keep-alive
  
  
  
  HTTP/1.1 401 Unauthorized
  Server: Microsoft-IIS/10.0
  X-Powered-By: ASP.NET
  Date: Thu, 10 Dec 2020 16:31:16 GMT
  Content-Length: 0
  </code></pre>
  <p>And if I call a the action that does not have the [Authorize] attribute, the HandleAuthenticateAsync function is called, but the action executes and returns a 200 even if the HandleAuthenticateAsync returned a AuthenticateResult.Fail.  I must be totally misunderstanding how this is supposed to work.</p>
  <pre><code>GET https://localhost:44321/Test/NoAuthenticationRequired HTTP/1.1
  User-Agent: PostmanRuntime/7.26.8
  Accept: */*
  Postman-Token: 81dd4c2a-32b6-45b9-bb88-9c6093f3675e
  Host: localhost:44321
  Accept-Encoding: gzip, deflate, br
  Connection: keep-alive
  
  
  HTTP/1.1 200 OK
  Content-Type: text/plain; charset=utf-8
  Vary: Accept-Encoding
  Server: Microsoft-IIS/10.0
  X-Powered-By: ASP.NET
  Date: Thu, 10 Dec 2020 16:35:36 GMT
  Content-Length: 3
  
  Ok!
  </code></pre>
  <p>I have a Controller with one action that I want to be authenticated on, and one I don't:</p>
  <pre><code>[ApiController]
  [Route(&quot;[controller]&quot;)]
  public class TestController : ControllerBase
  {
  
      private readonly ILogger&lt;TestController&gt; _logger;
  
      public TestController(ILogger&lt;TestController&gt; logger)
      {
          _logger = logger;
      }
  
      [HttpGet(&quot;RequiresAuthentication&quot;)]
      [Authorize]
      public string RestrictedGet()
      {
          return &quot;Ok!&quot;;
      }
  
      [HttpGet(&quot;NoAuthenticationRequired&quot;)]
      public string NonRestrictedGet()
      {
          return &quot;Ok!&quot;;
      }
  }
  </code></pre>
  <p>I have an authentication handler:</p>
  <pre><code>public class BasicAuthenticationHandler : AuthenticationHandler&lt;AuthenticationSchemeOptions&gt;
  {
      private IBasicAuthenticationService _authService;
  
      public BasicAuthenticationHandler(
              IOptionsMonitor&lt;AuthenticationSchemeOptions&gt; options,
              ILoggerFactory logger,
              UrlEncoder encoder,
              ISystemClock clock,
              IBasicAuthenticationService authService)
              : base(options, logger, encoder, clock)
      {
          ...
          _authService = authService;
      }
  
      protected override async Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync()
      {
          // skip authentication if endpoint has [AllowAnonymous] attribute
          var endpoint = Context.GetEndpoint();
  
          if (endpoint?.Metadata?.GetMetadata&lt;IAllowAnonymous&gt;() != null)
          {
              return AuthenticateResult.NoResult();
          }
  
          if (!Request.Headers.ContainsKey(&quot;Authorization&quot;))
          {
              return AuthenticateResult.Fail(&quot;Missing Authorization Header.&quot;);
          }
  
          IBasicAuthenticationServiceUser user = null;
          try
          {
              var authHeader = AuthenticationHeaderValue.Parse(Request.Headers[&quot;Authorization&quot;]);
              var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
              var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
              var username = credentials[0];
              var password = credentials[1];
              user = await _authService.Authenticate(username, password);
          }
          catch
          {
              return AuthenticateResult.Fail(&quot;Invalid Authorization Header.&quot;);
          }
  
          if (user == null)
          {
              return AuthenticateResult.Fail(&quot;Invalid Username or Password.&quot;);
          }
  
          var claims = new[] {
                  new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                  new Claim(ClaimTypes.Name, user.UserName),
              };
  
          var identity = new ClaimsIdentity(claims, Scheme.Name);
          var principal = new ClaimsPrincipal(identity);
          var ticket = new AuthenticationTicket(principal, Scheme.Name);
  
          return AuthenticateResult.Success(ticket);
      }
  }
  </code></pre>
  <p>I have an authentication service that authenticates against active directory:</p>
  <pre><code>class BasicAuthenticationActiveDirectoryService : IBasicAuthenticationService
  {
      private class User : IBasicAuthenticationServiceUser
      {
          public string Id { get; set; }
          public string UserName { get; set; }
          public string Lastname { get; set; }
          public string FirstName { get; set; }
          public string Email { get; set; }
      }
  
      public async Task&lt;IBasicAuthenticationServiceUser&gt; Authenticate(string username, string password)
      {
          string domain = GetDomain(username);
  
          using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
          {
              // validate the credentials
              bool isValid = pc.ValidateCredentials(username, password);
  
              if (isValid)
              {
                  User user = new User()
                  {
                      Id = username,
                      UserName = username
                  };
  
                  UserPrincipal up = UserPrincipal.FindByIdentity(pc, username);
  
                  user.FirstName = up.GivenName;
                  user.Lastname = up.Surname;
                  user.Email = up.EmailAddress;
  
                  return user;
              }
              else
              {
                  return null;
              }
          }
      }
  
      private string GetDomain(string username)
      {
          if (string.IsNullOrEmpty(username))
          {
              throw new ArgumentNullException(nameof(username), &quot;User name cannot be null or empty.&quot;);
          }
  
          int delimiter = username.IndexOf(&quot;\\&quot;);
          if (delimiter &gt; -1)
          {
              return username.Substring(0, delimiter);
          }
          else
          {
              return null;
          }
      }
  }
  </code></pre>
  <p>I wire this up in my startup.cs:</p>
  <pre><code>public void ConfigureServices(IServiceCollection services)
  {
      // configure DI for application services
      services.AddScoped&lt;IBasicAuthenticationService, BasicAuthenticationActiveDirectoryService&gt;();
  
      //Set up basic authentication
      services.AddAuthentication(&quot;BasicAuthentication&quot;)
          .AddScheme&lt;Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions, BasicAuthenticationHandler&gt;(&quot;BasicAuthentication&quot;, null);
  
      ...
  }
  </code></pre>
  <p>I setup authentication and authorization in my startup.cs:</p>
  <pre><code>public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
      if (env.IsDevelopment())
      {
          app.UseDeveloperExceptionPage();
      }
  
     ...
  
      app.UseHttpsRedirection();
  
      app.UseRouting();
  
      app.UseAuthorization();
      app.UseAuthentication();
  
      app.UseEndpoints(endpoints =&gt;
      {
          endpoints.MapControllers();
      });
  }
  </code></pre>