I am trying to set Register and Login for Hot Towel SPA applicantion. I have created SimpleMembershipFilters and ValidateHttpAntiForgeryTokenAttribute based on the asp.net s
I struggled a bit with this as neither of the existing answers seemed to work correctly for the case of my Durandal SPA app based on the Hot Towel Template.
I had to use a combination of Evan Larson's and curtisk's answers to get something that worked the way I think its supposed to.
To my index.cshtml page (Durandal supports cshtml alongside html) I added the following just above the
tag
@AntiForgery.GetHtml();
I added a custom filter class as suggested by Evan Larson, however I had to modify it to support looking up the cookie value separately and utilize __RequestVerificationToken as the name rather than RequestVerificationToken as this is what is supplied by AntiForgery.GetHtml();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using System.Web.Http.Filters;
using System.Net.Http;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Threading;
using System.Net.Http.Headers;
namespace mySPA.Filters
{
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public Task ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation)
{
if (actionContext == null)
{
throw new ArgumentNullException("HttpActionContext");
}
if (actionContext.Request.Method != HttpMethod.Get)
{
return ValidateAntiForgeryToken(actionContext, cancellationToken, continuation);
}
return continuation();
}
private Task FromResult(HttpResponseMessage result)
{
var source = new TaskCompletionSource();
source.SetResult(result);
return source.Task;
}
private Task ValidateAntiForgeryToken(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation)
{
try
{
string cookieToken = "";
string formToken = "";
IEnumerable tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("__RequestVerificationToken", out tokenHeaders))
{
formToken = tokenHeaders.First();
}
IEnumerable cookies = actionContext.Request.Headers.GetCookies("__RequestVerificationToken");
CookieHeaderValue tokenCookie = cookies.First();
if (tokenCookie != null)
{
cookieToken = tokenCookie.Cookies.First().Value;
}
AntiForgery.Validate(cookieToken, formToken);
}
catch (System.Web.Mvc.HttpAntiForgeryException ex)
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}
}
}
Subsequently in my App_Start/FilterConfig.cs I added the following
public static void RegisterHttpFilters(HttpFilterCollection filters)
{
filters.Add(new ValidateJsonAntiForgeryTokenAttribute());
}
In Application_Start under my Global.asax I added
FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);
Finally for my ajax calls I added a derivation of curtisk's input lookup to add a header to my ajax request, in the case a login request.
var formForgeryToken = $('input[name="__RequestVerificationToken"]').val();
return Q.when($.ajax({
url: '/breeze/account/login',
type: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(data),
headers: {
"__RequestVerificationToken": formForgeryToken
}
})).fail(handleError);
This causes all of my post requests to require a verification token which is based upon the cookie and hidden form verification tokens created by AntiForgery.GetHtml();
From my understanding this will prevent the potential for cross site scripting attacks as the attacking site would need to be able to supply both the cookie and the hidden form value to be able to verify themselves, which would be far more difficult to acquire.