7

This beautiful abstraction lets you place @Html.AntiForgeryToken() in cshtml file which is magically expanded to something like;

<input name="__RequestVerificationToken" type="hidden" value="JjMHm5KJQ/qJsyC4sgifQWWX/WmADmNvEgHZXXuB07bWoL84DrmQzE6k9irVyFSJ5VSYqeUIXgl4Dw4NHSotLwflGYTyECzLvrgzbtonxJ9m3GVPgUV7Z6s2Ih/klUB78GN7Fl4Gj7kxg62MEoGcZw175eVwTmkKJ0XrtEfD5KCVvYIMHNY8MT2l+qhltsGL87c9dII42AVoUUQ2gTvfPg==" />

By mvc before the page is served. However my page has some JavaScript making ajax calls which don't include the token even though it's been added to the form. They are currently getting the expected [HttpAntiForgeryException]: A required anti-forgery token was not supplied or was invalid. because they don't have the token. I'm aware I could parse the value out of the DOM but I shouldn't have to. Are there other ways of accessing/getting this value? To be clear, I mean I'd like an overload of the method that returns just the value as a string or some kind of object that has the name and value both.

To provide a bit more context my form and the relevant JS looks a little like this;

<form action="/settings" method="post"><input name="__RequestVerificationToken" type="hidden" value="JjMHm5KJQ/qJsyC4sgifQWWX/WmADmNvEgHZXXuB07bWoL84DrmQzE6k9irVyFSJ5VSYqeUIXgl4Dw4NHSotLwflGYTyECzLvrgzbtonxJ9m3GVPgUV7Z6s2Ih/klUB78GN7Fl4Gj7kxg62MEoGcZw175eVwTmkKJ0XrtEfD5KCVvYIMHNY8MT2l+qhltsGL87c9dII42AVoUUQ2gTvfPg==" />    <fieldset>
        <h3>User Settings</h3>
        <ul>
            <li>
            label for="password">Password</label>
                <a href="#" id="change_password" class="changePasswordButton">Edit</a>
                <div id="password_section" class="inlineedit">
                    <div>
                        <span for="existing_password">Current password</span> <input autocomplete="off" class="required" id="existing_password" name="existing_password" type="password" />
                    </div>
                    <div>
                        <span for="new_password">New password</span> <input autocomplete="off" class="required" id="new_password" name="new_password" type="password" />
                        <span id="password_strength" />
                    </div>
                    <div>
                        <span for="confirm_password">Confirm password</span> <input autocomplete="off" class="required" id="confirm_password" name="confirm_password" type="password" />
                    </div>
                    <div class="inlinesave">
                        <input type="button" value="Change" onclick="onPostChangePassword();"/>
                        <a href="#" id="cancel_password" class="cancel">Cancel</a>
                    </div>
                </div>
            </li>
    // a bunch more of these that call their own onPostChangeSetting method

onPostChangePassword() does some input validation then;

 if(validPWD && validNewPWD && validConfirmPWD && current_pwd != new_pwd){
                        // Post the password change
                        var currentAjaxRequest = $.ajax({
                            type: "POST",
                            url: "/settings/preferences/changepassword",
                            cache: false,
                            data: {password: $('#new_password').val(), current: $('#existing_password').val(),confirm: $('#confirm_password').val()},
                            success: password_success,
                            error: password_error,
                            dataType: "json"
                        });
                        return true;
                  }

Which ideally (since this is verbatim in a cshtml file) would be modified with something like this;

data: {password: $('#new_password').val(), current: $('#existing_password').val(),confirm: $('#confirm_password').val(),
__RequestVerificationToken:@Html.AntiForgeryValue() }

tl;dr is there a way to interact with the AntiForgeyToken before it's turned into an string of html?

evanmcdonnal
  • 46,131
  • 16
  • 104
  • 115
  • Curious: if you're "parsing" _other_ data for an XHR (e.g. `password`) why _"don't you have to"_ (for the token)? Wouldn't the answers essentially end up having to (generate and send it in a POST request _anyway_)? – EdSF Jul 09 '15 at 00:44
  • @EdSF by don't have to I mean I should be able to work with the data as an object server side so I don't have JS intercepting the ajax call, parsing a value out of the DOM, adding it as a header then letting execution continue. When the page is served I should be able to embed the value in the ajax call just as I have in the HTML. Sadly, you can't directly but that's just because the library is poorly designed. – evanmcdonnal Jul 09 '15 at 15:58
  • Still curious :) Based on that (and the answer below) which _seems_ to change how things work, I'm feeling a little insecure :) **If** I'm reading it right, I can reuse that value (replay, mock, etc.)? – EdSF Jul 09 '15 at 19:23
  • @EdSF it prevents a replay. The only way you could replay with a token in my cookie or DOM is if you've already exposed a cross-site-scripting vulnerability so that you're parsing my DOM or reading my cookies. Neither of those things are allowed by the browser. – evanmcdonnal Jul 09 '15 at 19:48

3 Answers3

8

You can use code like this (in for example _Layout.cshtml) to add the AntiForgery header to all Ajax POST requests, or you could adapt it for a specific request. (Code assumes you are using jQuery)

@functions{
    private static string TokenHeaderValue()
    {
        string cookieToken, formToken;
        AntiForgery.GetTokens(null, out cookieToken, out formToken);
        return cookieToken + ":" + formToken;
    }
}

<script type="text/javascript">
    $(document).ajaxSend(function (event, jqxhr, settings) {
        if (settings.type == "POST") {   
            jqxhr.setRequestHeader('@ValidateHttpAntiForgeryTokenAttribute.RequestVerificationTokenName',
                                   '@TokenHeaderValue()');
        }
    });
</script>

On the server side for these Ajax calls, you then want to call the overload of AntiForgery.Validate that takes the cookie and form token, which you would enable by adding this attribute to the action methods called via Ajax (explicitly, or by parent controller, or via a filter)

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
                AllowMultiple = false, Inherited = true)]
public sealed class ValidateHttpAntiForgeryTokenAttribute 
                                 : FilterAttribute, IAuthorizationFilter

{
    public const string RequestVerificationTokenName = "RequestVerificationToken";

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            ValidateRequestHeader(filterContext.HttpContext.Request);
        }
        else
        {
            AntiForgery.Validate();
        }
    }

    private static void ValidateRequestHeader(HttpRequestBase request)
    {
        string cookieToken = string.Empty;
        string formToken = string.Empty;

        var tokenValue = request.Headers[RequestVerificationTokenName];

        if (string.IsNullOrEmpty(tokenValue) == false)
        {
            string[] tokens = tokenValue.Split(':');

            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        }

        AntiForgery.Validate(cookieToken, formToken);
    }
stuartd
  • 70,509
  • 14
  • 132
  • 163
  • I didn't quite use this (the server side code wasn't necessary) but I ended up adding an extension method to HtmlHelper and used it like in the example above. – evanmcdonnal Jul 09 '15 at 16:00
0

Look at the Html.AntiForgeryToken() in ILSpy and copy out the code that builds an input tag and create your own extension method that returns only the token string.

BhavO
  • 2,406
  • 1
  • 11
  • 13
0

Short answer: there is not a native way to do what you're trying to, but as a previous answer states, you can create an HTML helper method.

This guy seems to have a working solution: https://stackoverflow.com/a/16057568/724222

Community
  • 1
  • 1
Carlitrosss
  • 176
  • 8