Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC5 Basic HTTP authentication and AntiForgeryToken exception

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.

like image 222
Franko Avatar asked Oct 27 '15 10:10

Franko


3 Answers

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.

like image 151
Andrew Avatar answered Oct 15 '22 10:10

Andrew


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

Response.ClearHeaders();
like image 1
Minh Nguyen Avatar answered Oct 15 '22 10:10

Minh Nguyen


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();
like image 1
Dusty Avatar answered Oct 15 '22 08:10

Dusty