46

What steps must be done to implement basic authentication in ASP.NET MVC 5?

I have read that OWIN does not support cookieless authentication, so is basic authentication generally possible?

Do I need a custom attribute here? I am not sure about how these attributes work.

2
  • 1
    Sometimes the solution is on another question, someone already did it here on on stackoverflow, there is the complete code: stackoverflow.com/questions/9043831/… Commented Jan 16, 2014 at 16:42
  • Cookies and Authentication are not related. One can use the other, but neither are dependent on the other. Commented Oct 18, 2018 at 1:31

9 Answers 9

87

You can use this simple yet effective mechanism using a custom ActionFilter attribute:

public class BasicAuthenticationAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected string Username { get; set; }
    protected string Password { get; set; }

    public BasicAuthenticationAttribute(string username, string password)
    {
        this.Username = username;
        this.Password = password;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        var auth = req.Headers["Authorization"];
        if (!String.IsNullOrEmpty(auth))
        {
            var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        /// thanks to eismanpat for this line: http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/#comment-2507605761
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

It can be used to put under Basic Authentication a whole controller:

[BasicAuthenticationAttribute("your-username", "your-password", 
    BasicRealm = "your-realm")]
public class HomeController : BaseController
{
   ...
}

or a specific ActionResult:

public class HomeController : BaseController
{
    [BasicAuthenticationAttribute("your-username", "your-password", 
        BasicRealm = "your-realm")]
    public ActionResult Index() 
    {
        ...
    }
}

In case you need additional info check out this blog post that I wrote on the topic.

Sign up to request clarification or add additional context in comments.

12 Comments

This technique worked for me even for hybrid authentication e.g. both Basic & Forms authentication in the same website.
Just a word of caution that this attribute won't work for Web API - you may be better off with a filter - weblog.west-wind.com/posts/2013/Apr/18/…
For odd reasons, I wanted to combine basic auth with forms authentication. This resulted in an infinite redirect loop: When my filter returned 401, forms tried to redirect back to login page. The filter then ran again, and again returned 401. The fix was to remove the LoginPath property from CookieAuthenticationOptions, and just rely on the <forms loginUrl="..."> element in Web.config
@MacakM : The realm attribute (case-insensitive) is required for all authentication schemes which issue a challenge. The realm value (case-sensitive), in combination with the canonical root URL of the server being accessed, defines the protection space. These realms allow the protected resources on a server to be partitioned into a set of protection spaces, each with its own authentication scheme and/or authorization database. [from RFC 1945 (HTTP/1.0) and RFC 2617]
Combining this with a form authentication can cause a problem : The 401 of basic auth redirecting to the form auth login page. To avoid that, just add the line : filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true; before the line filterContext.Result. You'll need .NET 4.5 or more.
|
13

You can do this with a custom attribute. There is an implementation of a custom attribute that supports base authentication in the open source project SimpleSecurity, which you can download here. There is a reference application to demonstrate how it is used. It was originally developed to work with SimpleMembership in MVC 4 and has been recently ported to use ASP.NET Identity in MVC 5.

Comments

9

I wanted to amend the answer shared by Darkseal, because that code has a major security flaw. As written, that action filter does not actually terminate the request when res.End() is called. The user is prompted for credentials and a 401 response is returned if the credentials don't match, but the controller action is still executed on the server side. You need to set the filterContext.Result property to something in order for the request to terminate properly and not continue to the action method.

This was particularly bad for my situation, as I was trying to protect a web service endpoint that receives a data feed from a third party. As written, this action filter didn't protect anything because the data was still being pushed through my action method.

My "quick fix" is below:

public class BasicAuthenticationAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected string Username { get; set; }
    protected string Password { get; set; }

    public BasicAuthenticationAttribute(string username, string password)
    {
        this.Username = username;
        this.Password = password;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        var auth = req.Headers["Authorization"];
        if (!String.IsNullOrEmpty(auth))
        {
            var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        var res = filterContext.HttpContext.Response;
        res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

2 Comments

The "quick fix" has been already applied in the main answer by @Darkseal and the problem with res.end() has been removed replaced with HttpUnauthorizedResult()
It seems that adding the filterContext.Result = new HttpUnauthorizedResult(); causes asp to redirect the user to the default login page, instead of letting a auth popup appear.
4

Great answer from @Darkseal. Here's the same code repurposed for use with ASP.NET Web API (close cousin to MVC). Same idea, slightly different namespaces and context classes. Add it to your classes and methods in exactly the same way.

using System.Web.Http.Controllers;
using System.Web.Http.Filters;

public class BasicAuthenticationAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected string Username { get; set; }
    protected string Password { get; set; }

    public BasicAuthenticationAttribute(string username, string password)
    {
        Username = username;
        Password = password;
    }

    public override void OnActionExecuting(HttpActionContext filterContext)
    {
        var req = filterContext.Request;
        var auth = req.Headers.Authorization;
        if (auth?.Scheme == "Basic")
        {
            var cred = Encoding.ASCII.GetString(Convert.FromBase64String(auth.Parameter)).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", BasicRealm ?? "YourRealmName"));
    }
}

1 Comment

3

HTTP basic authentication doesn't require a cookie. It's based on a HEADER in the HTTP request. The header is named Authorization and its value should be username and password combined into a string, "username:password" (all base64 encoded).

Sincerely I never used basic authentication with ASP.NET MVC, but I used Web API to create a custom attribute (you can start from here for WebAPI or here for MVC).

3 Comments

But for an Mvc application, you would need to store in Cookie. For instance every request will not have the header automatically appended. The client browser needs to add the authorization header, but it won't. WebApi is different, we have control over the HttpClient.
@harsimranb there hasn't been a mainstream browser that didn't keep adding the authorization header since the mid 90s. You likely have a bug on the server side.
@harsimranb WebApi isn't different; client browsers always adds authorization header, storing in cookie is unrelated to MVC and is a choice
1

you can try this package on Nuget (AuthPackage) its enables you to add authentication to your asp.net mvc easily.

  1. install package using Package Manager Console:

    Install-Package AuthPackage

  2. add Connection String to your Web.config in (appSettings):

     <add key="connectionString" value="connectionStringHere" />
    
  3. you're ready to register users, login, logout

example:

 public async Task<ActionResult> SignIn()
    {
        var context = System.Web.HttpContext.Current;
        AuthUser authUser = new AuthUser(context);
        await authUser.SignIn("[email protected]", "123456");
        return RedirectToAction("Index", "Home");
    }

You can read the Documentation here

1 Comment

You should mention that you are the author of the package
1

The Darkseal’s answer

[BasicAuthenticationAttribute("your-username", "your-password", 
    BasicRealm = "your-realm")]

has 2 disadvantages : name and password are hardcoded and they support only single user.

More flexible solution should support multiple username/password pairs stored in configuration.

Microsoft describes a sample https://gm/aspnet/samples/tree/main/samples/aspnet/WebApi/BasicAuthentication.

public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter

In overload of

abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,   
CancellationToken cancellationToken);   

you can implement check to find if username/password from the header exist in configuration/secret list of username/password pairs

It’s also possible to create HTTP module that performs Basic Authentication. You can easily plug in an ASP.NET membership provider by replacing the CheckPassword method. https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/basic-authentication#basic-authentication-with-custom-membership

Example of OWIN implementation https://github.com/scottbrady91/Blog-Example-Classes/tree/master/OwinBasicAuthentication/WebApi

Possible implementation in .Net core is described in https://github.com/mihirdilip/aspnetcore-authentication-basic

Comments

0

An application of ours "accidentally" used basic authentication because of the following code in Web.config:

<system.webServer>
    <modules>
        <remove name="FormsAuthentication" />
    </modules>
    ... other stuff
</system.webServer>

The application is otherwise configured to use forms authentication. The browser authentication window popped up whenever normal forms authentication would otherwise have been used.

Comments

0

This is a slight variation on the accepted answer. My scenario is that I have a n Asp.net (4.5.2) Controller which works as both an API, as well as a page that just returns to say "You have successfully connected to the Server." The purpose of this is so that the Basic Auth could be tested to ensure it is working before trying to use it with the API. Note that this is a legacy app and requirements stipulate the use of basic auth which is why it is still needed.

The problem is that the rest of the site is set up with FormsAuthentication, so when the BasicAuthenticationFilter would run, it would change the result from a 401 unauthorized to a 302 redirect and the user would end up on the login page.

What I really wanted was for the Basic Auth popup to show in the browser. Again this is only so the end user could test the credentials. This is what I had to change to get this to work:

    public override void OnActionExecuting(ActionExecutingContext    filterContext)
{
     var req = filterContext.HttpContext.Request;
     var auth = req.Headers["Authorization"];
     if (!String.IsNullOrEmpty(auth))
     {
         var cred =System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
              var user = new { Name = cred[0], Pass = cred[1] };
              if (user. Name == Username && user. Pass == password) return;
          }

     filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
     filterContext.HttpContext.Response.StatusCode = 401;
     filterContext.HttpContext.Response.End();
}

Note that using this line:

filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;

did NOT actually suppress the Forms Redirect ( don't know why, seems like it should).

  [HttpGet]
  [BasicAuthentication("TestUserName", "Abc123*")]
  public ActionResult Submit()
  {
      return new ContentResult() { Content = "You have successfully connected to the AP&G GISB Server." };
  }

Also note that the actual implementation checks a database for the username and password, the above is just demo code.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.