How do I post simple JSON data with a file upload? - c#

I'm trying to set up a file upload request in a ServiceStack TypeScript client that also includes the month for which the file is relevant. How do I set up the request so that both come through to the server?
I've tried various changes, including manually changing headers to try to force Content-Type to be application/json, which didn't work (but I suspect would break the file upload even if it did).
Client-side API:
export const serviceApi = {
importData: (month: string, file: File) => {
var client = new JsonServiceClient("");
var request = new DTOs.ImportData();
// At this point, the month has a value
request.month = month.replace('/', '-').trim();
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
const promise = client.postBody(request, formData);
return from(promise);
},
};
DTO definition:
[Route("/api/data/import/{Month}", "POST")]
public class ImportData : IReturn<ImportDataResponse>
{
public string Month { get; set; }
}
public class ImportDataResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
}
Server-side API:
[Authenticate]
public object Post(ImportData request)
{
if (Request.Files == null || Request.Files.Length <= 0)
{
throw new Exception("No import file was received by the server");
}
// This is always coming through as null
if (request.Month == null)
{
throw new Exception("No month was received by the server");
}
var file = (HttpFile)Request.Files[0];
var month = request.Month.Replace('-', '/');
ImportData(month, file);
return new ImportDataResponse();
}
I can see that the file is coming through correctly on the server side, and I can see an HTTP request going through with the month set in the query string parameters as "07-2019", but when I break in the server-side API function, the month property of the request is null.
Update, here are the HTTP Request/Response headers:
Request Headers
POST /json/reply/ImportData?month=07-2019 HTTP/1.1
Host: localhost:40016
Connection: keep-alive
Content-Length: 7366169
Origin: http://localhost:40016
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryI8CWlbw4tP80PkpZ
Accept: */*
Referer: http://localhost:40016/data
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: _ga=GA1.1.673673009.1532913806; ASP.NET_SessionId=gtwdk3wsvdn0yulhxyblod3g; __utmc=111872281; __utmz=111872281.1533684260.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); ss-opt=perm; __utma=111872281.673673009.1532913806.1550789161.1550794391.20; _gid=GA1.1.893581387.1558389301; ss-id=kfq4G0GYb3WldSdCaRyJ; ss-pid=aZ400sqM4n3TQgNVnHS2
Response Headers
HTTP/1.1 500 Exception
Cache-Control: private
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
X-Powered-By: ServiceStack/5.10 NET45 Win32NT/.NET
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RTpcVEZTXFNvdXJjZVxNZWRpc2VuXFdlYnNpdGVzXE9OaWlDU1xNYWluXFNvdXJjZVxPbmlpY3NSZWFjdC1QYXltZW50c1xPbmlpY3NSZWFjdFxPbmlpY3NSZWFjdFxqc29uXHJlcGx5XEltcG9ydE1CU0NvZGVz?=
X-Powered-By: ASP.NET
Date: Tue, 21 May 2019 21:49:03 GMT
Content-Length: 605
Query String Parameters
month=07-2019

You'll be able to upload a file using JavaScript's fetch API directly, e.g:
let formData = new FormData();
formData.append('description', file.name);
formData.append('type', 'file');
formData.append('file', file);
fetch('/api/data/import/07-2019', {
method: 'POST',
body: formData
});
Otherwise if you want to use ServiceStack's TypeScript JsonServiceClient you would need to use the API that lets you post the Request DTO with a separate request body, e.g:
formData.append('month', '07-2019');
client.postBody(new ImportData(), formData);

I don't think the month should be part of the request header, that's kinda unorthodox. It should be part of the form data.
If you did:
formData.append('Month', month.replace('/', '-').trim());
client side, then request.Month or request.content.Month should work, depending on how the request object is handled in your instance.

Related

HttpClient Post to API without [FromBody]

I'm looking to send complex objects to my .NET Core API without FromBody tags.
What I am looking to do is simple with JQuery, but I can't figure out for the life of me how to duplicate the logic from a C# Web API Client.
For the sake of testing, I have a fairly simple object.
[Serializable]
public class FilterModel
{
public int? PageSize { get; set; }
public int Page { get; set; }
}
For the sake of testing, we also have an extremely simple controller method
[HttpPost("TEST")]
public virtual int GetTEST(Common.FilterModel filter, Common.FilterModel filter2)
{
return ((filter.Page * filter.PageSize ?? 0) + (filter2.Page * filter2.PageSize ?? 0));
}
Obviously the use case for the code would me more complex, but I am just trying to get the values at this time.
The JQuery to call this method is fairly straight forward and works flawlessly:
var myData = {"filter":{"pageSize":3,"page":2},
"filter2":{"pageSize":19,"page":1}};
$.ajax({
type: 'POST',
url: '/api/Authentication/TEST',
data: myData
}).done(function (data, statusText, xhdr) {
JsonResponse = data;
console.log(data);
}).fail(function (xhdr, statusText, errorText) {
//console.log(JSON.stringify(xhdr));
});
But trying to duplicate that logic from an HttpClient results in 0; the model is always empty when received on the API side. I've tried a few different methods, from JSON Serializing an ArrayList/Dictionary to what I'm adding below, but I'm hitting a dead end unsure where I went wrong.
HttpClient client = new HttpClient
{
BaseAddress = new Uri("http://localhost:65447/")
};
ApiCommon.FilterModel filter = new ApiCommon.FilterModel()
{
PageSize = 10,
Page = 1
};
ApiCommon.FilterModel filter2 = new ApiCommon.FilterModel()
{
PageSize = 9,
Page = 41
};
MultipartFormDataContent mpContent = new MultipartFormDataContent();
StringContent content = new StringContent(JsonConvert.SerializeObject(filter));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
mpContent.Add(content, "filter");
content = new StringContent(JsonConvert.SerializeObject(filter2));
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
mpContent.Add(content, "filter2");
var retval =
JsonConvert.DeserializeObject<int>(
client.PostAsync("/api/Authentication/TEST", mpContent)
.Result.Content.ReadAsStringAsync().Result);
Console.WriteLine(retval);
Is there any way to imitate the JQuery call to work through an HttpClient? Am I missing something critical here?
EDIT:
After getting fiddler to pick it up, this is the raw data for each, I am going to try to match this with my changes.
JQuery fiddler
ePOST http://localhost:65447/api/Authentication/TEST HTTP/1.1
Host: localhost:65447
Connection: keep-alive
Content-Length: 86
Accept: */*
Origin: http://localhost:65447
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:65447/TestHarness
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
filter%5BpageSize%5D=3&filter%5Bpage%5D=2&filter2%5BpageSize%5D=19&filter2%5Bpage%5D=1
C# fiddler
POST http://local.dev:65447/api/Authentication/TEST HTTP/1.1
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Host: local.dev:65447
156
{"filter":{"<IncludeFullData>k__BackingField":false,"<SortBy>k__BackingField":"","<PageSize>k__BackingField":10,"<Page>k__BackingField":1,"<Operation>k__BackingField":""},"filter2":{"<IncludeFullData>k__BackingField":false,"<SortBy>k__BackingField":"","<PageSize>k__BackingField":9,"<Page>k__BackingField":41,"<Operation>k__BackingField":""}}
0

NotFound() returns 200 instead of 404

I've been stuck on this recently and can't figure out why this is happening.
I'm using an MVC Controller in .Net Core to return a NotFound() "404" response.
However, client side (using angular) if I console.log the response, it shows this...
status:200
statusText:"OK"
Is there any reason why returning NotFound() would return an error code of 200 instead of the intended 404?
This is my Controller GET.
// GET: api/cause/cause-name
[HttpGet("{name}")]
[AllowAnonymous]
public IActionResult GetCauseByName(string name)
{
var input = _service.GetCauseByName(name);
if (input == null)
{
return NotFound();
}
else
{
return Ok(input);
}
}
Any help would be appreciated! Thanks!
To be clear, for this instance assume input is null. What I'm testing is it hitting NotFound() not the return OK(input). Breakpoints have been set and it does hit the NotFound() but still returns the response code of 200.
Headers--
GET /cause/dsdasdas
HTTP/1.1
Host: localhost:48373
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/‌​webp,/;q=0.8 Accept-Encoding: gzip, deflate, sdch, br Accept-Language: en-US,en;q=0.8
HTTP/1.1
200 OK
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip Vary:
Accept-Encoding Server: Kestrel X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcaXR0ZW1wNVxEZXNrdG9wXFByb2plY3RGdW5kQX‌​BwXHNyY1xQcm9qZWN0Rn‌​VuZFxjYXVzZVxkc2Rhc2‌​Rhcw==?= X-Powered-By: ASP.NET Date: Thu, 25 May 2017 14:51:29 GMT –
POSTMAN HEADERS
Content-Encoding →gzip
Content-Type →text/html; charset=utf-8
Date →Thu, 25 May 2017 15:18:31 GMT
Server →Kestrel
Transfer-Encoding →chunked
Vary →Accept-Encoding
X-Powered-By →ASP.NET
X-SourceFiles →=?UTF-8?B?QzpcVXNlcnNcaXR0ZW1wNVxEZXNrdG9wXFByb2plY3RGdW5kQXBwXHNyY1xQcm9qZWN0RnVuZFxjYXVzZVxkc2Rhc2Rhcw==?=
I have asked a similar question and received some kind of answer... NotFound() doesn't seem to work as expected
The solution Redirect("~/404.html"); returns 200.
However, there's another way.
// Wherever you want to return your standard 404 page
return Redirect("Home/StatusCode?code=404");
public class HomeController : Controller
{
// This method allows for other status codes as well
public IActionResult StatusCode(int? code)
{
// This method is invoked by Startup.cs >>> app.UseStatusCodePagesWithReExecute("/Home/StatusCode", "?code={0}");
if (code.HasValue)
{
// here is the trick
this.HttpContext.Response.StatusCode = code.Value;
}
//return a static file.
try
{
return File("~/" + code + ".html", "text/html");
}
catch (FileNotFoundException)
{
return Redirect("Home/StatusCode?code=404");
}
}
}
This does return 404.

RestSharp - issue with post request (Operation failed)

I have a problem with post request with RestSharp. I have 2 classes:
public class UnitToPost
{
public bool floating_point { get; set; }
public Dictionary<string, TranslationUnitToPost> translations { get; set; }
}
public class TranslationUnitToPost
{
public string name { get; set; }
}
And I want to send it with post request:
client = new RestClient(adresApi);
client.AddDefaultHeader("Authorization", "Bearer " + key);
IRestRequest updateProduct = new RestRequest("units", Method.POST);
ShoperModel.UnitToPost unitToPost = new ShoperModel.UnitToPost();
unitToPost.floating_point = true;
ShoperModel.TranslationUnitToPost transUnit = new ShoperModel.TranslationUnitToPost();
transUnit.name = "namename";
unitToPost.translations = new Dictionary<string, ShoperModel.TranslationUnitToPost>();
unitToPost.translations.Add("pl_PL", transUnit);
updateProduct.RequestFormat = RestSharp.DataFormat.Json;
updateProduct.AddBody(unitToPost);
IRestResponse updateProductResponse = this.client.Execute(updateProduct);
And I always get an error:
[RestSharp.RestResponse] = "StatusCode: InternalServerError,
Content-Type: application/json, Content-Length: -1)"
Content =
"{\"error\":\"server_error\",\"error_description\":\"Operation
Failed\"}"
What is the cause of it? Could it be because of Dictionary in my class?
I've run your code and it issues a request with a valid JSON body.
POST http://..../units HTTP/1.1
Accept: application/json,application/xml, text/json, text/x-json, text/javascript, text/xml
Authorization: Bearer a
User-Agent: RestSharp/105.2.3.0
Content-Type: application/json
Host: .....
Content-Length: 84
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
{"floating_point":true,"translations":[{"Key":"pl_PL","Value":{"name":"namename"}}]}
It looks like the problem may be with the receiving server. If you're not doing so already I'd suggest running Fiddler (http://www.telerik.com/fiddler) and inspecting the request/response.
Edit...
I only just realised you want the JSON body to be :-
{"floating_point":true,"translations":{"pl_PL":{"name":"namename"}}}
I did find a RestSharp issue that covers this :-
https://github.com/restsharp/RestSharp/issues/696
This includes a post where someone has used an ExpandoObject to get the required result.
http://theburningmonk.com/2011/05/idictionarystring-object-to-expandoobject-extension-method/
However, I found it easier to use JSON .NET to serialise and set the body with the following code:-
updateProduct.AddBody(JsonConvert.SerializeObject(unitToPost));

Kendo Scheduler - Read datasource - Post array of strings to Web API

I am trying to accomplish following, get appointments of a user through POST request as I need to post other calendar ids to get appointments of other users as well. The POST request is sent to a Web API. The endpoint gets hit but the array of calendarIds is always empty.
This is the datasource definition:
dataSource: new kendo.data.SchedulerDataSource({
batch: true,
transport: {
read: {
url: "/api/MyCalendar/GetAppointments",
dataType: "json",
type: "POST"
},
parameterMap: function(data, type) {
if (type === "read") {
return JSON.stringify(data);
}
}
}
This is the Web API implementation:
[HttpPost]
public HttpResponseMessage GetAppointments(string[] calendarIds)
{
// calendarIds is always empty
This the request posted content (textView) from fiddler:
{"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]}
I am not sure what is wrong in here, thanks for any help on this.
Update:
The whole Raw request:
POST http://xxxxx/api/MyCalendar/GetAppointments HTTP/1.1
Host: ccmspatientmanager
Connection: keep-alive
Content-Length: 56
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://xxxxx
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://xxxxx/Home/MyCalendar
Accept-Encoding: gzip,deflate,sdch
Accept-Language: cs,en-GB;q=0.8,en;q=0.6,de-DE;q=0.4,de;q=0.2,sk;q=0.2
Cookie: ASP.NET_SessionId=flcab5ecct1zaopgqmpz0rhg; .ASPXAUTH=DAED17623F4B0E8F4AB0C3176EC0B73DD29A65650E93DB9664D52C9D23D34C52F1B312923B0A5F8A0D66DAF5C72864BF5827CC667D181DDE5EBC43C651D3C41FBFF315884DD74272E74E4A08D0D2380696B1C5B6
{"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]}
You may try annotating your WebAPI post method with [FromBody] attribute
[HttpPost]
public HttpResponseMessage GetAppointments([FromBody]string[] calendarIds)
Also make sure you are passing in an Array in the request body instead of an Object.
What you are sending right now {"calendarIds":["1c78e75f-9516-42cf-a439-271ee997abf1"]} is an object whereas the WebAPI method accepts an Array
You can try:
parameterMap: function(data, type) {
if (type === "read") {
var values = data.calendarIds.split(','),
return JSON.stringify(values);
}
}

Google chrome losing MVC Auth Cookie (Set-Cookie directive) in subsequent calls

I'm using ASP.net Core 1, MVC 6. I am using SignInManager and UserManager, to authenticate a user in a web api application (MVC6 / C#) from another MVC application (the web api Logon method is actually called from a Jquery Ajax request).
In IE, I call the Login method and when successful, it gives me a Set-Cookie response with an ASP.net auth cookie. I can then see subsequent requests have the ASP.net auth cookie attached.
In chrome, the Set-Cookie directive is returned in the response, but subsequent requests do not have the cookie attached.
Why is this happening?
The only difference I can see is that in Chrome, there is a pre-flight OPTIONS request being sent, but I have handled that in the startup.cs file in the web api and am essentially ignoring it.
Internet Explorer
My request to Login web api looks like this:
Accept */*
Accept-Encoding gzip, deflate
Accept-Language en-IE
Cache-Control no-cache
Connection Keep-Alive
Content-Length 246
Content-Type application/x-www-form-urlencoded; charset=UTF-8
Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM
DNT 1
Host localhost:44338
Referer https://localhost:44356/
Request POST /api/account/Login HTTP/1.1
User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
Response like this:
Response HTTP/1.1 200 OK
Cache-Control no-cache
Pragma no-cache
Content-Type application/json; charset=utf-8
Expires -1
Server Kestrel
Set-Cookie oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A; path=/; secure; httponly
X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
X-Powered-By ASP.NET
Access-Control-Allow-Methods GET,PUT,POST,DELETE
Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
Date Fri, 06 May 2016 14:23:22 GMT
Content-Length 16
Subsequent test web api call (IsLoggedIn):
Request GET /api/account/IsLoggedIn HTTP/1.1
X-ACL-Key 4A6F0007-95E7-4423-B786-6FBF981799FE
Accept */*
Referer https://localhost:44356/
Accept-Language en-IE
Accept-Encoding gzip, deflate
User-Agent Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Host localhost:44338
DNT 1
Connection Keep-Alive
Cache-Control no-cache
Cookie BeaeN4tYO5M=CfDJ8KMNkK4F2ylMlo1LFxNzxWLNDECVWfhxBYRQrw_MkNQBrVIwfO6FoMIMqg1PP-nZa8Dhp3IV1ZS1uXKpknUDYegiMlEvFaNG-wqUXErvQ5wkMMc_HBI88j-7bCbD2Q7P_B6fEQOQSTKHoL5sTcH0MoM; oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoQWhLxXcnaNkAMTB-VvpkMRIz2AiM_7feoIM29gza_zZz97qaE6TKdqK8y1jDPjDDyiiMdOMiuCmCoV5X4IQ9xtHvpGgmFoxOSiYFVeVOBbHsLx4BccL647F9sJ07M55zvjMx_7wrt32omhONH64vmc12P3nepwZjNSIFYfom1U0Z4r4EX_0tZjKRH7FrdvO0PI2iY5SMaKhCcBw1QXpQHSUxL6Hm-Wr8Q46gFAYoa6YffJV0Rx80FvJHmr1LMAA6PAF0dU_DzNdRVHdXm14t_nbfl-6xb6o7WQN259moUhkT1ZQ9CZsYwWvn7VBmpjfIXNJvIu0FDnRaHnNMrj3uN77_cAMdO3OcyCuy-CAKJ9c-0PxKToStb9juGSNa9ClpVQPADzpUxFqxZU029AXBPavXQK2Ezvy7YT4FwCkL8TEf5AnB5hfOZ5YCBlqD30n2heMdHDbXRHpxeaQB4aoY_6uSpJ3cPazBDsbvGi4fV2-0g5NvoTGgJUXa5p4UntRmuiJ2tZHbMmEjXzf-GV6QtTFIhseKsS3n6TMX68yqQOhYOzxvHdJXPjYxvjmm6-vJw5w2FDgiEXoQJQ7qaSmGzRwOA_cE4VBV_RhzrZELmp3A
Response like this:
Response HTTP/1.1 200 OK
Content-Type application/json; charset=utf-8
Server Kestrel
X-SourceFiles =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
X-Powered-By ASP.NET
Access-Control-Allow-Methods GET,PUT,POST,DELETE
Access-Control-Allow-Headers Content-Type,x-xsrf-token,X-ACL-Key
Date Fri, 06 May 2016 14:23:22 GMT
Content-Length 68
CHROME
My request to Login web api looks like this:
POST /api/account/Login HTTP/1.1
Host: localhost:44338
Connection: keep-alive
Content-Length: 246
Accept: */*
Origin: https://localhost:44356
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
Referer: https://localhost:44356/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Vary: Origin
Server: Kestrel
Set-Cookie: oAuthInterop=CfDJ8Asqo6qO2cNHlXpsdNLsuoRvlRjfUBWrkt3W3NzBJIoFYA6DcQivnfYmZV2O5xuiqpd75oRjZ-JeHBcjiOK0HoFJQ9f61RyJ2HDeuCNmQk0H-pA3Lzs5ft_F49dpQt0kFn3_-FzEh5-NScCbY4N6TiuYlWY4VSoKsdJJ91k7Z4LQO-0Wm3cZ6HfX0E6pLzGG4lWaZGuV-gOsVCRygR5nv_O_YpWwfaLsT_51aX6fNXVSotU6MECEkFdfWseqOGyYVj7KJrxY2mPwksE0XGACs12TnmfJzCABrzd06FnTPy3RuqJF2IWOobX6ZAHGMoAVFR07mhy9gMPyaHQ12RKmhBhZSXE-Yi3BHow2ER9d2Niligx7JjwYR7UfHFHWJdoYzewLRkZZGE5pw67O710hYyA2UCM2ODB9l9x-WDQ1A_3xjxu2Mrkp0lrF0V-h3y6V2gzEP9RyQAjDISEEZQqvb-GzfZrsRzzQcMn0TMhq5_LUKkX3AScSGRiarBzZ2O9Af3jzwTmN1BciJknJwMKRefq_zrXH7kymCD1kJM89aGkswqp2bycMQjlsjqg5k8EEhv8u1kLA7hA9NyE2ZaamB1PAWYz4NXi3Agccgw83nFi4bs6VE8ZLnyZFEwxdyEGyvQ; path=/; secure; httponly
Access-Control-Allow-Origin: https://localhost:44356
Access-Control-Allow-Credentials: true
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XExvZ2lu?=
X-Powered-By: ASP.NET
Access-Control-Allow-Methods: GET,PUT,POST,DELETE
Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
Date: Fri, 06 May 2016 12:59:36 GMT
Content-Length: 16
Subsequent test web api call (IsLoggedIn):
GET /api/account/IsLoggedIn HTTP/1.1
Host: localhost:44338
Connection: keep-alive
Accept: */*
Origin: https://localhost:44356
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
X-ACL-Key: 4A6F0007-95E7-4423-B786-6FBF981799FE
Referer: https://localhost:44356/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Response like this:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcUm9iZXJ0XERlc2t0b3BcSEJFIE1hbmFnZXJcTUFJTlxCbHVlem9uZSBXZWJBcGlcc3JjXEJ6LkFwcGxpY2F0aW9uXEJ6LkFwcGxpY2F0aW9uLkFwaVx3d3dyb290XGFwaVxhY2NvdW50XElzTG9nZ2VkSW4=?=
X-Powered-By: ASP.NET
Access-Control-Allow-Methods: GET,PUT,POST,DELETE
Access-Control-Allow-Headers: Content-Type,x-xsrf-token,X-ACL-Key
Date: Fri, 06 May 2016 12:59:43 GMT
My web api controller code looks like this:
[Authorize]
[EnableCors("AllowAll")]
[Route("api/[controller]")]
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(UserLogin model)
{
if (ModelState.IsValid) {
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) {
return Json(new { success = true });
}
if (result.RequiresTwoFactor) {
return Json(new { success = false, errType = 1 });
}
if (result.IsLockedOut) {
return Json(new { success = false, errType = 2 });
} else {
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Json(new { success = false, errType = 3 });
}
}
return Json(new { success = false, errType = 0 });
}
[HttpGet("IsLoggedIn")]
public IActionResult IsLoggedIn()
{
return Json(new {
loggedon = (HttpContext.User.Identity.Name != null && HttpContext.User.Identity.IsAuthenticated),
isauthenticated = HttpContext.User.Identity.IsAuthenticated,
username = HttpContext.User.Identity.Name
});
}
}
Startup.cs for my web api looks like this:
public class Startup
{
public static int SessionLength { get; private set; }
private string Connection;
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
SessionLength = 30;
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Get the configured connection string.
Connection = Configuration["Data:DefaultConnection:ConnectionString"];
var userStore = new CustomUserStore();
var roleStore = new CustomRoleStore();
var userPrincipalFactory = new CustomUserPrincipalFactory();
services.AddInstance<IUserStore<ApplicationUser>>(userStore);
services.AddInstance<IRoleStore<ApplicationRole>>(roleStore);
services.AddInstance<IUserClaimsPrincipalFactory<ApplicationUser>>(userPrincipalFactory);
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents() {
OnRedirectToAccessDenied = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
},
OnRedirectToLogin = ctx =>
{
if (ctx.Response.StatusCode == (int)HttpStatusCode.Unauthorized || ctx.Response.StatusCode == (int)HttpStatusCode.Forbidden) {
return Task.FromResult<object>(null);
}
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
};
//options.Cookies.ApplicationCookie.CookieHttpOnly = false;
options.Cookies.ApplicationCookieAuthenticationScheme = "ApplicationCookie";
options.Cookies.ApplicationCookie.AuthenticationScheme = "ApplicationCookie";
options.Cookies.ApplicationCookie.CookieName = "oAuthInterop";
options.Cookies.ApplicationCookie.AutomaticChallenge = true;
options.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
options.Cookies.ApplicationCookie.DataProtectionProvider = new DataProtectionProvider(new DirectoryInfo("d:\\development\\artefacts"),
configure =>
{
configure.SetApplicationName("TestAuthApp");
//configure.ProtectKeysWithCertificate("thumbprint");
});
options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromMinutes(SessionLength);
}).AddDefaultTokenProviders();
// Add framework services.
services.AddMvc();
// Add cross site calls.
//TODO: implement with better security instead of allowing everything through.
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader().AllowCredentials()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc();
}
}
A wild guess would be you are not setting withCredentials flag on your XMLHttpRequest when making cross-domain request from javascript via ajax. This flag basically controls whether to include credentials (such as cookies, authorization headers or client certificates) in cross-domain request. Why it still works in IE? Not completely sure, but maybe because proper implementation of this flag only appeared in IE10, and you might use another version of IE. If you use jquery to make requests, see here how to set this flag.
If that is not the case, please include your client-side code + request and response headers of Chrome's OPTIONS request.

Categories

Resources