ASP.NET MVC5 Basic HTTP authentication and AntiForgeryToken exception
Asked Answered
T

3

11

I'm working on ASP.NET MVC5 project which has forms authentication enabled. Project is currently in test phase, and hosted online on Azure, but project owner would like to disable all public access to the site (since some parts of site don't require for user to authenticate at all).

For this test phase, we've decided to implement basic HTTP authentication, from this link. I've changed the code, so it better suits my needs:

public class BasicAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
    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 void OnAuthorization (AuthorizationContext 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;
        var alreadySent = HttpContext.Current.Items.Contains("headers-sent");

        if (!alreadySent)
        {
            res = filterContext.HttpContext.Response;
            res.StatusCode = 401;
            res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));

        }
    }
}

I've also registered it as a global filter:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorExtendedAttribute());
        filters.Add(new BasicAuthenticationAttribute(AppConfig.BasicUsername, AppConfig.BasicPassword));
    }
}

However, there are some issues when I run the project. If I use this version of code:

        if (!alreadySent)
        {
            res = filterContext.HttpContext.Response;
            res.StatusCode = 401;
            res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));
        }

after successfull login it constantly redirects to forms login page.

However if I append

res.End();

after

res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));

Antiforgerytoken in cshtml files throws System.Web.HttpException

Server cannot append header after HTTP headers have been sent.

But in this case, it eventually successfully authenticates.

I'm currently stuck on this, and have no idea how to solve this problem, since turning off forms authentication is not and option, and I can't remove all AntiForgeryTokens and their validation.

Tomekatomes answered 27/10, 2015 at 10:40 Comment(3)
Why did you choose this approach? If you want to build a custom Authentication Filter, you should inherit from AuthorizeAttribute, override IsAuthorized function, return false for unauthorized requests and handle them in HandleUnauthorizedRequest function.Lannielanning
If you flip res.StatusCode line with the res.AppendHeader line what would happen? Does the StatusCode value count as part of the "Body" so that closes the Headers from being written to any more? Also please consider Andrew's response below.Mildred
plz don't use basic auth. Note that other question you reference is like 6 years old, and even then there were better alternatives. See: adrianotto.com/2013/02/why-http-basic-auth-is-badHannis
A
3

I would suggest you to use ASP.NET Identity membership provider since it is included in MVC 5. Using this you can simply authenticate users without writing a lot of code as it was with previous Membership. It can also use external login with internal cookies as if you use FormsAuthentication method. All that you need is to make simple configurations in code and you don't need to write your custom filters.

Antinucleon answered 2/11, 2015 at 12:20 Comment(0)
E
1

Have you tried this line before res.AppendHeader(..) ?

Response.ClearHeaders();
Estreat answered 2/11, 2015 at 11:43 Comment(0)
V
1

Instead of ending the request with req.End(), I think you want to set filterContext.Result as shown below. Setting the Result to a non-null value will interrupt the processing of the rest of the MVC pipeline and cause the response to be sent to the browser, but it should not seal the response as End() does, so you shouldn't get the exception about headers already being sent.

Try this:

filterContext.Result = new HttpUnauthorizedResult();
Vershen answered 5/11, 2015 at 20:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.