1

i'm trying to access a WebAPI which is using ValidateAntiForgeryToken. My WebAPI Method is this (a simple one), which is inside a User Controller (just for a test):

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(String field)
{
    String result = String.Empty;
    if (ModelState.IsValid)
    {
        HtmlSanitizer sanitizer = new HtmlSanitizer();

        try
        {
            result = sanitizer.Sanitize(field);
        }
        catch (Exception ex)
        {
            result = ex.Message;
            throw;
         }
    }
    return Json(result);
}

With Ajax, i can access it with ease:

$.ajax({
    url: '/User/Test',
     type: "POST",
    contentType: "application/x-www-form-urlencoded",

    data: {

        field: self.textField(),
         __RequestVerificationToken: $("input[name='__RequestVerificationToken']").val(),
    },
    success: function(e) {
       self.textField(e)
        self.divField(e);
    },
    error: function(e) {
        console.log(e.error());
    },
});

But, until now, i can't access this webapi with httpclient on xamarin. This is my code:

    private async void DoTestWebApi()
{
    try
    {
        HttpClient clientPage = new HttpClient()
        {
            BaseAddress = new Uri("https://localhost:44356/user")
        };

        var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);

        String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());

        HttpClient client = new HttpClient()
        {
            BaseAddress = new Uri("https://localhost:44356/user/test/")
        };

        HttpRequestMessage message = new HttpRequestMessage()
        {
            RequestUri = new Uri("https://localhost:44356/user/test/"),
            Method = HttpMethod.Post
        };

        message.Headers.Add("__RequestVerificationToken", verificationToken);

        String field = "teste";

        //StringContent content = new StringContent("field=test", Encoding.UTF8, "application/x-www-form-urlencoded");
        StringContent content = new StringContent("__RequestVerificationToken=" + verificationToken + ",field=test", Encoding.UTF8, "application/x-www-form-urlencoded");

        // this doesn't work
        //client.DefaultRequestHeaders.Add("__RequestVerificationToken", verificationToken);
        var response2 = await client.SendAsync(message);

        if (response2.IsSuccessStatusCode)
        {
            var t = response2.Content.ReadAsStringAsync();

            if (true)
            {
                // just to check if t has value
            }
        }

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }

}

Honestly, i don't know what else i could do to pass my anti forgery token inside the message. It works perfectly in ajax, i pass it inside the data content, but in xamarin it doesn't work. All the code is executed inside the same localhost. If i remove the [ValidateAntiForgeryToken], it works.

What am i missing?

Edit:

Ok, so now i'm sending with cookies, but is not hitting the method again. This is my update:

HttpClient clientPage = new HttpClient()
{
    BaseAddress = new Uri("https://localhost:44356/user")
};

var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);

String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());

List<KeyValuePair<String, String>> cookiesInfo = new List<KeyValuePair<String, String>>();

foreach (var item in pageWithToken.Headers)
{
    cookiesInfo.Add(new KeyValuePair<String, String>(item.Key, item.Value.ToString()));
}

cookiesInfo.Add(new KeyValuePair<string, string>("field", "value"));
cookiesInfo.Add(new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken));

CookieContainer cookieContainer = new CookieContainer();

using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
    using (var client = new HttpClient(handler) { BaseAddress = new Uri("https://localhost:44356/user") })
    {
        var content = new FormUrlEncodedContent(cookiesInfo);

        cookieContainer.Add(client.BaseAddress, new Cookie("__RequestVerificationToken", verificationToken));

        foreach (var item in cookiesInfo)
        {
            cookieContainer.Add(client.BaseAddress, new Cookie(item.Key, item.Value));
        }

        var result = client.PostAsync(new Uri("https://localhost:44356/user/test"), content).Result;

        result.EnsureSuccessStatusCode();
    }

};

This is driving me nuts... Ok the test is in localhost but soon this app will be in Azure, and this is a pre-requisite...


Edit: GetVerificationToken Method:

private string GetVerificationToken(String verificationToken)
    {
        if (verificationToken != null && verificationToken.Length > 0)
        {
            verificationToken = verificationToken.Substring(verificationToken.IndexOf("__RequestVerificationToken"));
            verificationToken = verificationToken.Substring(verificationToken.IndexOf("value=\"") + 7);
            verificationToken = verificationToken.Substring(0, verificationToken.IndexOf("\""));
        }

        return verificationToken;
    }
Gustavo Gonçalves
  • 528
  • 1
  • 12
  • 33
  • Can you capture the requests sent to your API using a tool like [Fiddler](http://www.telerik.com/fiddler) and compare the ajax one with the HttpClient. This may give you some clues as to what the differences are. – Callback Kid Feb 14 '17 at 20:06
  • `localhost`? localhost used within your phone/emulator would be the phone/emulator not your PC. Use a Hostname/IP address of the host that is running your aspnet/iis application that is resolvable from the phone/emulator, try using the device's browser to open that url/page as a test... – SushiHangover Feb 14 '17 at 20:08
  • yeah, localhost. because all apps are being executed in the same machine. And it works, that's not the problem (if i remove the ValidateAntiForgeryToken, runs fine) – Gustavo Gonçalves Feb 16 '17 at 13:48

3 Answers3

2

ValidateAntiForgeryToken is also expecting a cookie with __RequestVerificationToken and the value provided. This is to make sure that the one posting to the controller is the one who viewed the form.

Zroq
  • 8,002
  • 3
  • 26
  • 37
  • I've updated my question, but it seems that the cookie is not only the problem. I'm sending now with cookies. I'm trying with a tool called Advanced Rest Client (Chrome), and even if i set the cookies in this tool, it doesn't work. – Gustavo Gonçalves Feb 20 '17 at 20:15
2

Thanks to @Zroq tip, i've finally made it. The cookie was indeed missing. This is the final version of my method which sends data to a WebApi with AntiForgeryToken in Asp.NET MVC 5.0:

private async void DoTestWebApi()
    {
        try
        {
            CookieContainer cookieContainer = new CookieContainer();

            HttpClientHandler handlerhttps = new HttpClientHandler
            {
                UseCookies = true,
                UseDefaultCredentials = true,
                CookieContainer = cookieContainer
            };

            HttpClient clientPage = new HttpClient(handlerhttps)
            {
                BaseAddress = new Uri("https://localhost:44356/user")
            };

            var pageWithToken = await clientPage.GetAsync(clientPage.BaseAddress);

            String verificationToken = GetVerificationToken(await pageWithToken.Content.ReadAsStringAsync());

            var cookies = cookieContainer.GetCookies(new Uri("https://localhost:44356/user"));

            using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer, UseDefaultCredentials = true, UseCookies = true })
            {
                using (var client = new HttpClient(handler) { BaseAddress = new Uri("https://localhost:44356/user/test") })
                {
                    var contentToSend = new FormUrlEncodedContent(new[]
                    {
                        new KeyValuePair<string, string>("field", "value"),
                        new KeyValuePair<string, string>("__RequestVerificationToken", verificationToken),
                    });

                    var response = client.PostAsync(client.BaseAddress, contentToSend).Result;
                }
            };
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

Thanks again @Zroq.

Gustavo Gonçalves
  • 528
  • 1
  • 12
  • 33
  • Hi.Where is GetVerificationToken() method ? – paradise_human May 07 '17 at 17:25
  • Is there any way to send object instead of string ? I dont want to use Json.Deserialized on server side . because I want to use ModelState. – paradise_human May 13 '17 at 22:44
  • i'm not in my PC right now, but AFAIK you can send as StringContent, which you send JSON objects to your server: http://stackoverflow.com/questions/11145053/cant-find-how-to-use-httpcontent and http://stackoverflow.com/questions/18971510/how-do-i-set-up-httpcontent-for-my-httpclient-postasync-second-parameter This method expects only a JSON object or String, as far i can remember, you can't send your Custom Object into it. – Gustavo Gonçalves May 15 '17 at 12:42
  • Yes,I tried that methods but my object are always empty in server side. – paradise_human May 15 '17 at 16:13
  • how to put verification token to somthing like this : var stringContent = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(url, stringContent); – paradise_human May 15 '17 at 16:16
  • The case sensitive of your fields in your model, and the data you're sending, do they match? – Gustavo Gonçalves May 16 '17 at 03:03
  • Yes.I use this : var contentToSend = new FormUrlEncodedContent(nameValueCollection: new[] { new KeyValuePair(key: "item", value: json), new KeyValuePair(key: "__RequestVerificationToken", value: verificationToken) }); But when I want to get object in server side , my object is null. Note : json is "var json = JsonConvert.SerializeObject(value: item);" and item is an object – paradise_human May 16 '17 at 08:18
  • When I send a string instead of object , everything is worked fine. But When I use this : StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(url, stringContent); My server side detect object however I don't know how to put verification token in json to pass server token validation. – paradise_human May 16 '17 at 08:22
  • i see, now i understand completely your question. Unfortunately, i didn't come this far, but i remember of doing something like this in one of my tests: StringContent content = new StringContent(JsonConvert.SerializeObject(modelObj), Encoding.UTF8, "application/json"); where : Content is the json String you will send. Server expects the data coming from the cliente as something like 'string'. If it's a simple string, then it's without any formatted code. If it's a Model, then it's something like a Json Formatted String (if you're calling it like a web api, for example). – Gustavo Gonçalves May 16 '17 at 20:08
  • You can check these two links, they explain better my explanation: http://stackoverflow.com/questions/35344981/posting-to-a-web-api-using-httpclient-and-web-api-method-frombody-parameter-en And: http://stackoverflow.com/questions/36625881/is-possible-pass-an-object-to-postasync – Gustavo Gonçalves May 16 '17 at 20:09
1

For anyone that want the GetVerificationToken() body :

private string GetVerification(string responseBody)
{
     var data = QueryHelpers.ParseQuery(queryString: responseBody);
     string firstValue = data[key: "<input name"];
     var cutedValue = firstValue.Remove(startIndex: 0, count: 50);
     var result = cutedValue.Split('"')[0];
     return result;
}
paradise_human
  • 685
  • 2
  • 14
  • 31