1

Pretty much what the title says. Here's the javascript... Works fine when not validating the token. Doesn't appear to see it when validating as I get The required anti-forgery form field "__RequestVerificationToken" is not present. error.

var downloadEmailSignature = function (control) {
    if (control) {
        let form = $(control).closest('form');
        let token = $(form).find('input[name="__RequestVerificationToken"]').val();

        if (form) {
            let request = new XMLHttpRequest();
            request.open("POST", "Forms/DownloadEmailSignature");
            request.responseType = "blob";
            request.setRequestHeader('RequestVerificationToken', token);
            request.data = form.serialize();
            request.onload = function () {
                if (window.clientData.browser.name === "Internet Explorer") {
                    window.navigator.msSaveBlob(this.response, "EmailSignature.hta");
                }
                else{
                    let url = window.URL.createObjectURL(this.response);
                    let a = document.createElement("a");
                    document.body.appendChild(a);
                    a.href = url;
                    a.download = this.response.name || "download-" + $.now();
                    a.click();
                }
            };
            console.dir(request);
            request.send();
        }
    }
};

and the code behind...

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult DownloadEmailSignature(string emailSignature)
    {
        var hta = (MediaItem)SitecoreContext.GetItem<Item>(new Guid("{EE806F14-5BD3-491C-9169-DA701357FB45}"));

        using (var reader = new StreamReader(MediaManager.GetMedia(hta).GetStream().Stream))
        {
            var htaText = reader.ReadToEnd();

            htaText = htaText.Replace("*CARDSTRING*", emailSignature);

            var stream = new MemoryStream(htaText.ToASCIIByteArray());

            return new FileStreamResult(stream, "application/hta");
        }
    }

And finally the view...

<form id="download-email-signature" method="POST" enctype="multipart/form-data">
    @Html.HiddenFor(m => m.EmailSignatureMarkup)
    @Html.AntiForgeryToken()                                          
    @Html.FormIdentifier("FormsController", "DownloadEmailSignature")
    <a href="#" id="download-installer" onclick="downloadEmailSignature(this); return false;" class="btn-primary" style="margin-bottom:10px;margin-top:-10px;text-align:center;">Download installer</a>
</form>
Sean T
  • 2,414
  • 2
  • 17
  • 23
  • This: _Works fine when not validating the token._ would seem to indicate the token is not present or not valid. You set a custom HTTP header, but don't show how it is used or even that it is. What steps have you taken to ensure it is there and valid on both ends? Does the header contain the token? – Randy Casburn Jan 31 '19 at 14:30
  • @RandyCasburn it's present in the markup and I've logged `token` to console where it shows . Should have made that clear in the question – Sean T Jan 31 '19 at 14:32
  • and the header? – Randy Casburn Jan 31 '19 at 14:35
  • @RandyCasburn The header is present in the list of request headers. That's not the issue. The `[ValidateAntiForgeryToken]` annotation server side is supposed to look for this header. For whatever reason it can't see it. It's out of the box functionality of asp.net mvc – Sean T Jan 31 '19 at 15:15
  • Your `downloadEmailSignature` function works with hand coded HTML. Please provide the parsed HTML output that shows up in the browser. As a side note, on the `token=` assignment statement, `$()` is not needed around `form` as it is already a jQuery object. – Randy Casburn Jan 31 '19 at 15:16
  • I'm assuming the HTML is fine and the console.log(request) outputs a valid request will all the data intact - if that is correct, you can skip the HTML. And focus on the Server - under those circumstances, this is not a client problem. – Randy Casburn Jan 31 '19 at 15:18

2 Answers2

0

If you can replace XMLHttpRequest with $.ajax (as you already have JQuery loaded), the below segment should work.

let form = $(control).closest('form');
let token = $(form).find('input[name="__RequestVerificationToken"]').val();

$.ajax({
    url: '/Home/DownloadEmailSignature',
    type: 'POST',
    data: {
        __RequestVerificationToken: token,
        emailSignature: 'emailSignature value'
    },
    success: function (result) {
        alert(result);
        //your code ....
    }
});
user619656
  • 799
  • 2
  • 10
  • 18
  • I can't use $.ajax in this instance unfortunately as it doesn't support `responseType:'blob'`. I'll amend the af token though and pass through as part of the data rather than its own header and see – Sean T Jan 31 '19 at 15:26
  • Nah didn't work, it's bizarre I can see `&__RequestVerificationToken=`...and the token in the request being sent but server side can't. gonna shelve this and just set up a more traditional download method – Sean T Jan 31 '19 at 15:35
0

According to the answer here:

ValidateAntiForgeryToken purpose, explanation and example

MVC's anti-forgery support writes a unique value to an HTTP-only cookie and then the same value is written to the form. When the page is submitted, an error is raised if the cookie value doesn't match the form value.

Which implies the cookie MUST go back to the server. This should be working fine unless you are using IE9 (which is known to be problematic with this issue).

I recommend you include request.withCredentials = true; to eliminate any strange CORs related issue.

If a proxy is involved, the cookie may be getting stripped on the way back to the server too.

Randy Casburn
  • 13,840
  • 1
  • 16
  • 31
  • Cheers for the help but no joy. I'm just gonna have to put this down as another quirk of developing with sitecore. I've used ajax and appended af tokens in the past with no issue and for the life of me can't see why it wouldn't work. – Sean T Jan 31 '19 at 15:39
  • 1
    @SeanT - well bummer. I'll leave this answer here in case it helps someone else in the future. – Randy Casburn Jan 31 '19 at 15:48