How to Add Basic Authentication to ASP.NET Core MVC

A legacy .NET Core example of Basic authentication with an MVC filter and custom middleware, including its security limitations.

Article guideContents, topics, tags, and RSS
Get new notes via RSS

What Basic authentication provides

Basic authentication is a simple HTTP authentication scheme. A client sends a user name and password with each request in an Authorization header. It can still be useful in tightly controlled integrations, but it is usually a poor default for an interactive web application.

The credentials are Base64-encoded, not encrypted. Anyone who can observe an unprotected request can recover them, so HTTPS is mandatory. Browsers may also cache credentials and send them automatically, which makes logout behavior and cross-site request forgery protection less straightforward.

Advantages

  • Defined by the current RFC 7617 standard.
  • Widely supported by HTTP clients.
  • Simple for controlled service-to-service scenarios.

Limitations

  • Credentials are sent with every authenticated request.
  • Base64 offers no confidentiality.
  • Browser logout behavior is limited.
  • Automatically attached credentials can create CSRF risk.
  • The scheme does not provide modern token lifetimes, scopes, or revocation.

Historical implementations

The following filter and middleware examples are retained to explain the original 2018 implementation. They should not be copied into a new application without adding robust parsing, credential storage, constant-time comparison where appropriate, logging controls, and tests. Current ASP.NET Core applications should integrate a scheme through the authentication system.

Attribute and filter solution

The filter approach made it possible to opt in at controller or action level.

Example code

BasicAuthorizeAttribute.cs

// -------------------------------------------------------------------------------------------------
// Copyright (c) Johan Boström. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
// -------------------------------------------------------------------------------------------------

namespace API.BasicAuth.Attributes
{
  using System;
  using Microsoft.AspNetCore.Mvc;

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  public class BasicAuthorizeAttribute : TypeFilterAttribute
  {
    public BasicAuthorizeAttribute(string realm = null)
      : base(typeof(BasicAuthorizeFilter))
    {
      Arguments = new object[]
      {
        realm
      };
    }
  }
}

BasicAuthorizeFilter.cs

// -------------------------------------------------------------------------------------------------
// Copyright (c) Johan Boström. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
// -------------------------------------------------------------------------------------------------

namespace API.BasicAuth.Attributes
{
    using System;
    using System.Text;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;

    public class BasicAuthorizeFilter : IAuthorizationFilter
    {
        private readonly string realm;

        public BasicAuthorizeFilter(string realm = null)
        {
            this.realm = realm;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            string authHeader = context.HttpContext.Request.Headers["Authorization"];
            if (authHeader != null && authHeader.StartsWith("Basic "))
            {
                // Get the encoded username and password
                var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();

                // Decode from Base64 to string
                var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));

                // Split username and password
                var username = decodedUsernamePassword.Split(':', 2)[0];
                var password = decodedUsernamePassword.Split(':', 2)[1];

                // Check if login is correct
                if (IsAuthorized(username, password))
                {
                    return;
                }
            }

            // Return authentication type (causes browser to show login dialog)
            context.HttpContext.Response.Headers["WWW-Authenticate"] = "Basic";

            // Add realm if it is not null
            if (!string.IsNullOrWhiteSpace(realm))
            {
                context.HttpContext.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";
            }

            // Return unauthorized
            context.Result = new UnauthorizedResult();
        }

        // Make your own implementation of this
        public bool IsAuthorized(string username, string password)
        {
            // Check that username and password are correct
            return username.Equals("User1", StringComparison.InvariantCultureIgnoreCase)
                    && password.Equals("SecretPassword!");
        }
    }
}

Example usage

SecuredController.cs

// -------------------------------------------------------------------------------------------------
// Copyright (c) Johan Boström. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
// -------------------------------------------------------------------------------------------------

namespace API.BasicAuth.Controllers
{
    using System.Collections.Generic;
    using Attributes;
    using Microsoft.AspNetCore.Mvc;

    [BasicAuthorize("my-example-realm.com")]
    [Route("api/[controller]")]
    public class SecuredController : Controller
    {
        // GET api/secured
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new[] { "Secret 1", "Secret 2" };
        }
    }
}

Middleware solution

The alternative was a custom ASP.NET Core middleware. This sample secures the entire site, although middleware can be mapped to selected branches.

Example code

After creating the middleware, the original application registered it with app.UseMiddleware<BasicAuthMiddleware>("example-realm.com"); in Startup.cs.

BasicAuthMiddleware.cs

// -------------------------------------------------------------------------------------------------
// Copyright (c) Johan Boström. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
// -------------------------------------------------------------------------------------------------

namespace API.BasicAuth.Middlewares
{
    using System;
    using System.Net;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;

    public class BasicAuthMiddleware
    {
        private readonly RequestDelegate next;
        private readonly string realm;

        public BasicAuthMiddleware(RequestDelegate next, string realm)
        {
            this.next = next;
            this.realm = realm;
        }

        public async Task Invoke(HttpContext context)
        {
            string authHeader = context.Request.Headers["Authorization"];
            if (authHeader != null && authHeader.StartsWith("Basic "))
            {
                // Get the encoded username and password
                var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();

                // Decode from Base64 to string
                var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));

                // Split username and password
                var username = decodedUsernamePassword.Split(':', 2)[0];
                var password = decodedUsernamePassword.Split(':', 2)[1];

                // Check if login is correct
                if (IsAuthorized(username, password))
                {
                    await next.Invoke(context);
                    return;
                }
            }

            // Return authentication type (causes browser to show login dialog)
            context.Response.Headers["WWW-Authenticate"] = "Basic";

            // Add realm if it is not null
            if (!string.IsNullOrWhiteSpace(realm))
            {
                context.Response.Headers["WWW-Authenticate"] += $" realm=\"{realm}\"";
            }

            // Return unauthorized
            context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
        }

        // Make your own implementation of this
        public bool IsAuthorized(string username, string password)
        {
            // Check that username and password are correct
            return username.Equals("User1", StringComparison.InvariantCultureIgnoreCase)
                    && password.Equals("SecretPassword!");
        }
    }
}

What to use now

For new code, use ASP.NET Core’s authentication abstractions so the scheme participates consistently in challenges, authorization, dependency injection, logging, and testing. Derive a scheme handler from AuthenticationHandler<TOptions> if Basic authentication is genuinely required, or choose a standard token, cookie, or federated flow that fits the client.

The original examples remain in zarxor/Example.API.Secured for historical reference.

Whatever implementation you choose, do not send Basic credentials over plain HTTP.

Johan Boström smiling in a navy jacket and white shirt.
Author portrait

About the author

Johan Boström

Technology leader · Solution architect · Developer

My primary work is leading developers and working as a solution architect. I still develop when hands-on work helps the team or the solution.

I mainly work across systems, integrations, and AI. This archive keeps the practical details, lessons, and implementation notes worth finding again.