I'm using this code in my .NET API:
jwtOptions.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
Logger.LogException(c.Exception);
c.Response.StatusCode = 401;
c.Response.ContentType = "text/plain";
if (ApiConfig.IsDeveloping)
{
return c.Response.WriteAsync(c.Exception.ToString());
}
return c.Response.WriteAsync("An error occured processing your authentication.");
},
OnTokenValidated = c =>
{
if (c.Principal.IsInRole("SuperAdmin"))
{
c.HttpContext.Items["IsSuperAdmin"] = true;
}
return Task.CompletedTask;
}
};
But this causes my React client to not get the errors if the authentication token is invalid.
When I look at the Chrome's Network tab, I see this error:
(failed)net::ERR_HTTP2_PROTOCOL_ERROR
What should I do? What have I done wrong in my .NET code?
Update I realized that it depends on the token that I send. If I send a totally invalid token like lkjsdlkjf then I get the 401 response. But if I use a valid JWT token from internet that is invalid for my current session, I get this error.
Update 2
This is the request/response I logged using Wireshark. Request/response seem to be fine. But Chrome or Postman cannot understand it correctly:
GET /locale/data HTTP/1.1
X-Forwarded-Host: api.admin.example.local
X-Forwarded-Proto: https
Connection: Upgrade
Host: api.admin.example.local
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: 02c8e0f2-0afb-4112-8abf-c4815ea66176
Accept-Encoding: gzip, deflate, br
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
Date: Mon, 09 Jan 2023 07:47:32 GMT
Server: Kestrel
Cache-Control: no-cache, no-store, must-revalidate
Content-Encoding: br
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
Vary: Accept-Encoding
Milliseconds: 2238
33
...An error occured processing your authentication.
Related
I am struggling with this for weeks now, tried everything I could find on internet, but nothing works.
Biggest problems is - it works seamlessly in .NET 4.8 with default WCF scaffold, however .NET Core has WCF reworked and this one I can not figure out.
So, the authentication process has three parts.
Get XSFR token from web page, this is simple HttpClient.GetAsync towards web page which returns XSFR cookie in response.
Get SessionID by calling REST auth method, also simple and works with HttpClient.PostAsync.
Call HTTPS WCF service by sending XSFR token and SessionID as cookies. No go.
I used Visual Studio 2022 (17.4.3) to scaffold wsdl file.
I tried different bindings and I learned that Security.Mode has to be System.ServiceModel.SecurityMode.Transport otherwise I get error that "http" is expected but "https" found in URL and does not work.
However, when I do set security mode to Transport, I get error:
'The HTTP request was forbidden with client authentication scheme 'Anonymous'.'
Anonymous changes depending on Security.Transport.ClientCredentialType.
As for sending cookies, I tried two different methods:
OperationContextScope:
using (new OperationContextScope(port.InnerChannel)) {
var endPoint = new EndpointAddress(Config.URL_APP_ORIGIN);
var httpRequest = new HttpRequestMessageProperty();
httpRequest.Headers.Add(HEADER_X_XSRF_TOKEN, xsrfToken?.Value);
httpRequest.Headers.Add(HttpRequestHeader.Cookie, sessionId?.ToString());
httpRequest.Headers.Add(HttpRequestHeader.Cookie, xsrfToken?.ToString());
OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest);
await IspisVerzije(port);
// await IspisAllowedValues(port);
port.Close();
}
HttpHeaderMessageInspector
// Implementation of partial method in scaffolded web service reference
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials) {
var headers = new Dictionary<string, string> {
["HEADER_X_XSRF_TOKEN"] = Program.xsrfToken?.ToString(),
["Cookie"] = "Basic " + Program.sessionId?.ToString(),
["Cookie"] = "Basic " + Program.xsrfToken?.ToString()
};
var behaviour = new AddHttpHeaderMessageEndpointBehavior(headers);
serviceEndpoint.EndpointBehaviors.Add(behaviour);
}
}
public class HttpHeaderMessageInspector : IClientMessageInspector {
private Dictionary<string, string> Headers;
public HttpHeaderMessageInspector(Dictionary<string, string> headers) {
Headers = headers;
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
// ensure the request header collection exists
if (request.Properties.Count == 0 || !request.Properties.ContainsKey(HttpRequestMessageProperty.Name)) {
request.Properties.Add(HttpRequestMessageProperty.Name, new HttpRequestMessageProperty());
}
// get the request header collection from the request
var HeadersCollection = ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers;
// add our headers
foreach (var header in Headers) HeadersCollection[header.Key] = header.Value;
return null;
}
}
public class AddHttpHeaderMessageEndpointBehavior : IEndpointBehavior {
private IClientMessageInspector HttpHeaderMessageInspector;
public AddHttpHeaderMessageEndpointBehavior(Dictionary<string, string> headers) {
HttpHeaderMessageInspector = new HttpHeaderMessageInspector(headers);
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
clientRuntime.ClientMessageInspectors.Add(HttpHeaderMessageInspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
I used Fiddler to inspect outgoing requests and cookies are present in request.
This is raw request sent by working .NET Framework 4.8 version:
POST https://zupit-test.gov.hr/services/integration/v2 HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 4.0.30319.42000)
X-XSRF-TOKEN: 7280e4c2-6dd1-4ed4-9a35-91c22c172dfd
VsDebuggerCausalityData: uIDPo0dR90lQHnFDopiVLjbd/0IAAAAAxYMKfS4zuEWC0d5bf6byLKVx4ImkhWZLr+qCOPCMN3gACQAA
Content-Type: application/soap+xml; charset=utf-8; action=""
Host: zupit-test.gov.hr
Cookie: XSRF-TOKEN=7280e4c2-6dd1-4ed4-9a35-91c22c172dfd; JSESSIONID=B232706D460BE6C5FC5981826B36E9EB; TS01a2529c=01402d66b9a555cd86492a154020763ed07f0c7bbe2dbd6487ca2abfa0cfa1b3c533cde8a1335c973f7c6d5c24cdee6f743a38b5b5c0b26948152fee150d6abba41ea0e8cd
Content-Length: 301
Expect: 100-continue
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><getVersion xmlns="http://zupit.gov.hr/integration/v2" /></soap:Body></soap:Envelope>
And this is raw request sent by .NET 7 version:
POST https://zupit-test.gov.hr/services/integration/v2 HTTP/1.1
Host: zupit-test.gov.hr
Cache-Control: no-cache, max-age=0
X-XSRF-TOKEN: e62fe08b-e7ea-4fd7-88d7-28f58aa45018
Cookie: JSESSIONID=77C6A62B4A0B62DDDFAD8C534EA9D2A9,XSRF-TOKEN=e62fe08b-e7ea-4fd7-88d7-28f58aa45018
Accept-Encoding: gzip, deflate
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 573
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://zupit.gov.hr/integration/v2/IntegrationWSv2/getVersionRequest</a:Action><a:MessageID>urn:uuid:3aef24b7-6c08-4a85-bff4-fd0dbcf7ce1d</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand="1">https://zupit-test.gov.hr/services/integration/v2</a:To></s:Header><s:Body><getVersion xmlns="http://zupit.gov.hr/integration/v2"/></s:Body></s:Envelope>
And this is server response for .NET 7 request:
HTTP/1.1 403
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Set-Cookie: XSRF-TOKEN=915dea61-d5a4-4c63-ad85-51f92df31f67; Path=/
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Date: Thu, 22 Dec 2022 07:42:34 GMT
Strict-Transport-Security: max-age=16070400; includeSubDomains
Set-Cookie: TS01a2529c=01402d66b972abf2e18b994c3e4a4342cb1a60809c602d5bd0ebfb7953d22b3d635225a9b013d6108d2efd22d658d57c1de898f2709b920c1a01fefeda45b6c6746a57884d; Path=/
Transfer-Encoding: chunked
8e
{"timestamp":"2022-12-22T07:42:35.272Z","error":"Forbidden","message":"Forbidden","path":"/services/integration/v2","showServerMessage":false}
0
Is this HTTPS issue or difference of generated SOAP XML in .NET 7?
Any ideas how to solve this?
Thanks,
Mario
Okay, so I'm very new to using API's in code and I've been able to use a few that were actually pretty easy. But none of them required authentication. I've been trying to use Jira's REST API service via C#'s HttpClient class. See code below:
public void UpdateJiraIssue(string issueValue)
{
string url = $#"http://jira.mySite.com/rest/api/2/issue/{issueValue}/editmeta";
string jsonString = #"myNeatJsonData";
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
//Initialize Client
HttpClient apiClient = new HttpClient();
apiClient.BaseAddress = new System.Uri(url);
apiClient.DefaultRequestHeaders.Accept.Clear();
byte[] cred = UTF8Encoding.UTF8.GetBytes("username:password");
apiClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(cred));
apiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
async Task RunJiraAPI()
{
using (HttpResponseMessage resp = await apiClient.PostAsync("editmeta", content))
{
if (resp.IsSuccessStatusCode)
{
var jsonSring = await resp.Content.ReadAsStringAsync();
}
}
}
RunJiraAPI();
return;
}
The problem I run into is that I get a 401 error (Authentication). Here's what my 'resp' object contains when I run the code:
resp: {StatusCode: 401, ReasonPhrase: ' ', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
X-AREQUESTID: 400x1314x1
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
X-ASEN: SEN-11158344
X-AUSERNAME: anonymous
Cache-Control: no-store, no-transform, no-cache
Set-Cookie: atlassian.xsrf.token=B2ZY-C2JQ-1AGH-PBLW_5ccc79da5af8e6abcb9bff5250f3305af3b2877a_lout; Path=/; Secure
WWW-Authenticate: OAuth realm="https%3A%2F%2Fjira.mySite.com"
X-Powered-By: ARR/3.0
X-Powered-By: ASP.NET
Date: Wed, 15 Jan 2020 13:40:22 GMT
Content-Length: 109
Content-Type: application/json; charset=UTF-8
}}
Request Message: {Method: POST, RequestUri: 'https://jira.rhlan.com/rest/api/2/issue/RHD-1116/editmeta', Version: 1.1, Content: System.Net.Http.StringContent, Headers:
{
Authorization: Basic cWE6aGVjc29mdDEyMw==
Accept: application/json
Content-Type: Application/json; charset=utf-8
Content-Length: 70
}}
Status Code: Unauthorized
I need to work on my json string a bit to get it working right (which is why I didn't include what it actually contains), but once I get passed the authentication error, I'll probably actually change things to do a get Jira issue via the API so I can see all the json data returned that way. Then I'll edit my json string accordingly.
Any ideas what I'm doing wrong here?
You can pass in credentials assuming you have a username and an api token.
string credentials= string.Format("{0}:{1}", username, apitoken);
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(credentials);
And in your apiClient you can use it like this.
apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteCredentials));
You need a username and api-token. Your api-token should be your login password.
There is a problem writing AppProperties for the file using the Google drive api. The request is executed, then an error is returned 400.
Request is sent via dotnet api:
public Resource ResourcePatch(string FileID, IDictionary<string, string> properties)
{
GoogleAPI.File file = new GoogleAPI.File() { AppProperties = properties };
var updateRequest = _driveService.Files.Update(file, FileId);
updateRequest.Fields = "id, appProperties";
file = updateRequest.Execute();
...
This is what is sent and what I get.
Request:
PATCH https://www.googleapis.com/drive/v3/files/1DUpjXjNro6LreMgyVbYW7k05eBes0A3o?fields=id%2C%20appProperties HTTP/1.1
User-Agent: Sberdisk google-api-dotnet-client/1.38.0.0 (gzip)
Authorization: Bearer ...
Content-Type: application/json; charset=utf-8
Host: www.googleapis.com
Content-Length: 49
Accept-Encoding: gzip, deflate
{"appProperties":{"SHARED_PROPERTY_NAME":"true"}}
Response, sometimes:
HTTP/1.0 200 This buggy server did not return headers
<html><title>Error 400 (Bad Request)!!1</title></html>
Sometimes:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Date: Thu, 07 Feb 2019 12:39:13 GMT
Vary: Origin
Vary: X-Origin
Content-Type: application/json; charset=UTF-8
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE
Alt-Svc: quic=":443"; ma=2592000; v="44,43,39"
Content-Length: 183
{
"id": "1nWtqhBYKSl0YhtLMypEx5rmAuo43-3Ts",
"appProperties": {
"SHARED_PROPERTY_NAME": "true"
}
}
Sometimes the request is executed, sometimes an Bad Request. I do't see system logic in this. What can be the reason?
I am having a big problem when trying to authenticate to QuickBlox's server using a Token.
The method I use is:
public static async Task<LoginResponse> GetLoginResponseAsync(string email, string password)
{
LoginResponse result = null;
using (var client = new HttpClient())
{
string token = QbProvider.SessionResponse.Session.Token;
LoginRequest request = new LoginRequest()
{
Email = email,
Password = password
};
using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, LoginUrlRequest))
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("QB-Token", token);
using (var response = await client.SendAsync(requestMessage))
{
string json = response.Content.ReadAsStringAsync().Result;
result = JsonConvert.DeserializeObject<LoginResponse>(json);
}
}
}
return result;
}
The server's response is:
{"errors":["Token is required"]}
And the headers (debugging) in the client object are:
{
StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Date: Thu, 01 Jun 2017 12:05:29 GMT
QuickBlox-REST-API-Version: 0.1.1
Server: openresty/1.9.15.1
Status: 401 Unauthorized
Strict-Transport-Security: max-age=31536000
WWW-Authenticate: OAuth realm=users
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Request-Id: 584a0dca-fc44-4114-9626-327ac1729f67
X-Runtime: 0.003430
X-XSS-Protection: 1; mode=block
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Content-Length: 32
}
}
When I use the Token in Postman, the server's response is successfull.
Do you know what am I doing wrong?
Thank you so much in advance!
Regards.
Try adding your token header using requestMessage.Headers.Add("QB-Token", token) instead of your Authorization one – by #dukedukes
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.