4

I have a MVC website with some AngularJS components.

When I make a post request using angularjs, I always include the __RequestVerificationToken token from a hidden input on the page.

My problem is this:

User starts an anonymous session i.e not logged in.

The user logs in using angularjs component, which sends a post request. My MVC controller validates the credentials and the '__RequestVerificationToken'. Once the user is logged in, it returns a the new token.

The angularjs controller then takes the new token and updates the hidden input to be used for any future requests.

However the next request I make using angularjs fails the validation because the var tokenCookie = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryConfig.CookieName); (see below code sample) is still the old token from the anonymous session.

Although the "X-XSRF-Token" (see below code sample) is coming through as the new one.

How do I also update/renew the http cookie (tokenCookie) containing the token to the new one?

I have posted copy of my code below.

My action filter:

public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
            var headers = filterContext.HttpContext.Request.Headers;

            var tokenCookie = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryConfig.CookieName);

            var tokenHeader = string.Empty;
            if (headers.AllKeys.Contains("X-XSRF-Token"))
            {
                tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
            }

           AntiForgery.Validate(tokenCookie != null ? tokenCookie.Value : null, tokenHeader);

        base.OnActionExecuting(filterContext);
    }
}

My login controller:

[WebApiValidateAntiForgeryTokenAttribute]
[HttpPost]
public ActionResult login(string email, string password)
{
    if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password)) return new HttpUnauthorizedResult();

    var rq = HttpContext.Request;
    var r = validateLogin(email, password, true); // my login handling
    if (r.Success)
    {
        Response.StatusCode = (int)HttpStatusCode.OK;

        // within an action construct AJAX response and pass updated token to client
        return Json(new
        {
            __RequestVerificationToken = UpdateRequestVerificationToken(Request)
        });
    }
    else
    {
        return new HttpUnauthorizedResult();
    }
}

/// <summary>
/// resets AntiForgery validation token and update a cookie
/// The new antiforgery cookie is set as the results and sent
/// back to client with Ajax
/// </summary>
/// <param name="Request">request from current context</param>
/// <returns>string - a form token to pass to AJAX response</returns>
private string UpdateRequestVerificationToken(HttpRequestBase Request)
{
    string formToken;
    string cookieToken;
    const string __RequestVerificationToken = "__RequestVerificationToken";
    AntiForgery.GetTokens(Request.Form[__RequestVerificationToken], out cookieToken, out formToken);
    if (Request.Cookies.AllKeys.Contains(__RequestVerificationToken))
    {
        HttpCookie cookie = Request.Cookies[__RequestVerificationToken];
        cookie.HttpOnly = true;
        cookie.Name = __RequestVerificationToken;
        cookie.Value = cookieToken;
        Response.Cookies.Add(cookie);
    }
    return formToken;
}

My angularjs Login handling:

login(email, password) {
    return new Promise((resolve, reject) => {
        return this.AccountRequest.login(email, password)
            .then(response => {
                const newToken = response.data['__RequestVerificationToken'];
                const oldTokenElement = angular.element('input[name="__RequestVerificationToken"]');
                oldTokenElement.val(newToken); // confirmed the new token has been updated in the hidden element

                resolve(this.refresh.bind(this));
            });
    })
}

Every time I make a post request using angularjs:

post(uri, queryParams = {}, data = null) {

    this.$http.defaults.headers.common['X-XSRF-Token'] = angular.element('input[name="__RequestVerificationToken"]').attr('value');
    this.$http.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; // Needed to ensure we get back http error instead of a webpage
    const config = this.makeConfig('POST', uri, queryParams, data);

    return this.$http(config);
}

makeConfig(method, uri, params = {}, data = null) {
    return {
        method,
        data,
        params,
        url: uri.toString(),
    };
}
2
  • Is your AntiForgeryConfig.CookieName = "__RequestVerificationToken"? This is because the cookie that you get and set is different, hence why OnActionExecuting is still reading the old value . Commented Oct 22, 2018 at 4:40
  • The cookie and cookie name is set by MVC and yes it is __RequestVerificationToken. Could you clarify the reason for me? or point me to some documentation I could read up? any help is appreciated. thanks Commented Oct 22, 2018 at 10:13

1 Answer 1

1

Your code looks a bit off with how you are assigning the value to the existing cookie.

Your method has a mismatch of the token that the method returns (formToken) and what is being set in your cookie (cookieToken). Pay attention to the last line of the method...

const string __RequestVerificationToken = "__RequestVerificationToken";
HttpCookie cookie = Request.Cookies[__RequestVerificationToken];

// check for null
if (cookie == null)
{
    // no cookie found, create it... or respond with an error
    throw new NotImplementedException();
}

// grab the cookie
AntiForgery.GetTokens(
    Request.Form[__RequestVerificationToken],
    out string cookieToken, 
    out string formToken);

if (!String.IsNullOrEmpty(cookieToken)
{
    // update the cookie value(s)
    cookie.Values[__RequestVerificationToken] = cookieToken;
    //...
}

// update the expiration timestamp
cookie.Expires = DateTime.UtcNow.AddDays(30);

// overwrite the cookie
Response.Cookies.Add(cookie);

// return the NEW token!
return cookieToken;

Side note..

You probably shouldn't be using const here...

const newToken = response.data['__RequestVerificationToken']; // <-- doesn't look right...
const oldTokenElement = angular.element('input[name="__RequestVerificationToken"]');

A const will not allow you to reassign the value. Therefore, the newToken will never get reassigned the new token value at the client side. You might want to consider using a let or a var here instead.

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

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.