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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sonic
  • 815
  • 1
  • 8
  • 13
  • 1
    Sometimes the solution is on another question, someone already did it here on on stackoverflow, there is the complete code: http://stackoverflow.com/questions/9043831/authorizeattribute-with-roles-but-not-hard-coding-the-role-values/9048151#9048151 –  Jan 16 '14 at 16:42
  • Cookies and Authentication are not related. One can use the other, but neither are dependent on the other. – Erik Philips Oct 18 '18 at 01:31

8 Answers8

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.

Darkseal
  • 9,205
  • 8
  • 78
  • 111
  • 1
    This technique worked for me even for hybrid authentication e.g. both Basic & Forms authentication in the same website. – Anil Vangari Mar 02 '15 at 20:09
  • 7
    Just a word of caution that this attribute won't work for Web API - you may be better off with a filter - http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter – Henry C Apr 29 '15 at 16:04
  • 5
    Awesome. This was exactly what I needed! – irhetoric Jun 29 '15 at 18:19
  • 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 element in Web.config – Martin Eden Jul 07 '16 at 13:48
  • I would like to ask, what is the purpose of BasicRealm property? – MacakM Dec 23 '16 at 09:51
  • 2
    @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] – Darkseal Dec 24 '16 at 11:23
  • 5
    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. – Spilarix Mar 27 '17 at 18:47
  • @Darkseal How i can use the authentication in a httprequest? as headers? as what? – Anastasios Moraitis Sep 09 '17 at 10:32
  • This doesn't seem to work if normal logins are already enabled, it just redirects the user to the login page instead of requesting the username/password in a popup. – Douglas Gaskell Jun 15 '19 at 04:42
  • Yea I need to find a way to return a 401 instead of a 200 with a redirect to the login page.. – Zapnologica Jul 18 '20 at 14:54
  • To avoid the 200 redirect to login page you need to decorate the Action Method with the [AllowAnonymous] attribute, which will bypass the login-based authorization mechanism. – Darkseal Jul 20 '20 at 09:56
  • Make sure to include `ActionFilterAttribute` via `using System.Web.Mvc;` and NOT via `using System.Web.Http.Filters;`. – SNag Mar 16 '21 at 08:42
  • BasicAuthenticationAttribute is limited to single user with hardcoded name and password – Michael Freidgeim Apr 11 '21 at 12:31
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.

Kevin Junghans
  • 17,475
  • 4
  • 45
  • 62
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();
    }
}
PatrickGaule
  • 151
  • 1
  • 4
  • 2
    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()``` – starlocke Jun 01 '16 at 13:00
  • 1
    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. – Douglas Gaskell Jun 15 '19 at 05:01
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"));
    }
}
Neil Laslett
  • 2,019
  • 22
  • 22
  • See similar answer in https://stackoverflow.com/questions/23336204/how-use-authorizationfilterattribute-in-webapi-with-webclient-library – Michael Freidgeim Apr 16 '21 at 22:28
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).

Community
  • 1
  • 1
imperugo
  • 385
  • 1
  • 9
  • 4
    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 Aug 25 '14 at 22:21
  • @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. – Jon Hanna Jan 26 '18 at 10:29
  • @harsimranb WebApi isn't different; client browsers _always_ adds authorization header, storing in cookie is unrelated to MVC and is a _choice_ – Zimano Mar 10 '20 at 10:54
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("waleedchayeb2@gmail.com", "123456");
        return RedirectToAction("Index", "Home");
    }

You can read the Documentation here

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

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
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.

Charles Burns
  • 10,310
  • 7
  • 64
  • 81