When CORS is configured (and seemingly working) it still returns a 405 for the OPTIONS verb.
CORS is configured via the Owin NuGet package, Microsoft.Owin.Cors and it seems to return the correct headers.
I notice the issue in Fiddler when I fake a preflight OPTIONS request. The headers come back properly when I propose that I am calling from an allowed domain and they are correctly absent if I propose some other domain - its working.
But why the 405?
HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Allow: GET,PUT,DELETE
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: http://localhost:12345
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 01 Oct 2015 18:59:32 GMT
Content-Length: 96
<Error><Message>The requested resource does not support http method 'OPTIONS'.</Message></Error>
Note the Access-Control-Allow-Origin is present, so I guess the browser will be okay, or will the browser be 'put off' by the negative 405?
I think you may check first allowed verb's setting on the configurations of IIS.
refer to http://www.iis.net/configreference/system.webserver/security/requestfiltering/verbs
web.config example
<configuration>
<system.webServer>
<security>
<requestFiltering>
<verbs applyToWebDAV="false">
<add verb="PUT" allowed="false" />
</verbs>
</requestFiltering>
</security>
</system.webServer>
</configuration>
Whilst working on another problem, I reverse engineered the whole of Microsoft.Owin.Cors to step through it.
It turns out that if the middleware has handled the OPTIONS request then it should return a 2xx and indeed, with the new knowledge of exactly what the middleware needed to see in a request to determine that it was a CORS preflight request, I was able to fake one in Fiddler and get a 2xx.
So, if you get a 405, then double-check.
Related
I have a project using web api and one using asp.net mvc.
They are configured using CORS module.
The project using Web Api is configured to allow any origin because it's deployed on Azure and it's an OData endpoint, so we don't know who is going to consume it.
OData endpoint configuration
// inside configuration
config.EnableCors();
// controllers
[EnableCors(origins: "*", headers: "*", methods: "*")]
[Authorize]
[HttpPost]
public void ...
From my MVC client, using Angular $http we issue an authenticated request using Bearer token which looks like this one:
Request sent
Request URL:http://myodata.com/Products
Request Method:GET
Status Code:200 OK
Remote Address:123.123.123.123
And this is the header sent
Host: myodata.com
Authorization: bearer 123-ABC
Origin: http://myclient.com
Content-Type: application/json;charset=UTF-8
Accept: application/json, text/plain, */*
Referer: http://myclient.com/
The problem lays in the response as you can see here:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Because after we upgraded from 5.2.2 to 5.2.3 the CORS module is sending out Access-Control-Allow-Origin: *
And clearly, from the browser, we get this error:
XMLHttpRequest cannot load http://myodata.com/Products.
The value of the 'Access-Control-Allow-Origin' header
in the response must not be the wildcard '*'
when the request's credentials mode is 'include'.
Origin 'http://myclient.com' is therefore not allowed access.
The credentials mode of requests initiated by the XMLHttpRequest
is controlled by the withCredentials attribute.
But the call succeed actually, it bombs on the Angular side.
We didn't have this issue until today, after we upgraded from 5.2.2 to 5.2.3
One thing to try is setting the header using the URL Rewrite Module, by adding the following to your Web.config or ApplicationHost.config file in %SystemDrive%\inetpub\wwwroot\.
<configuration>
<system.webServer>
<rewrite>
<outboundRules>
<rule name="Make Access-Control-Allow-Origin echo Origin">
<match serverVariable="RESPONSE_Access-Control-Allow-Origin"
pattern=".+" negate="true" />
<action type="Rewrite" value="{HTTP_ORIGIN}" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
If the above doesn’t work, then you can try the version in the answer over at CORS in IIS issue with credentials and wildcard in Access-Control-Allow-Origin.
Another approach: in the global.asax or other code for your service, add something like:
if (ValidateRequest()) {
Response.Headers.Remove("Access-Control-Allow-Origin");
Response.AddHeader("Access-Control-Allow-Origin", Request.Headers["origin"]);
Response.Headers.Remove("Access-Control-Allow-Credentials");
Response.AddHeader("Access-Control-Allow-Credentials", "true");
Response.Headers.Remove("Access-Control-Allow-Methods");
Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
}
...the most important part of that being this:
Response.AddHeader("Access-Control-Allow-Origin", Request.Headers["origin"]);
In either case the end effect should be: your backend takes the value of the Origin request header and echoes it back as the Access-Control-Allow-Origin response-header value.
In my SSO service I'm redirecting the incoming request after validation which response message is
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /home?s=ZXVuPXN0YWZmdXNyMDE7ZXVwPXN0QGZmdXNSMDE7b2ZmaWNlaWQ9O29mZmljZT07Y3VzdD1PSzt0cz03MzUyNjIwNDIwO3ZlbmRvcj0xO3N1bj1zZWVrZXIwN0BkZW1vLmNvbTs=
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 27 Jan 2014 09:20:36 GMT
Content-Length: 261
<html><head><title>Object moved</title></head><body>
<h2>Object moved to here.</h2>
</body></html>
The above 302 message happens after bunch of redirects between client and server i have listed it below using fiddler
# Result Protocol Host URL Body Caching Content-Type Process Comments Custom
4 302 HTTPS client.net /ok/r/oauth/authorize?client_id=bededeb806f783b75ef94ad871a4e03e2c7aeb1fb316d8822233afaa664d17cb&redirect_uri=https%3A%2F%2Fexample.sso.com%2FOKJobMatch.aspx&state=hk1udegmnspyvum5obcwscwe&response_type=code 232 no-cache, private text/html; charset=utf-8 iexplore:5324
Suceeded
6 301 HTTPS example.sso.com /OKJobMatch.aspx?code=c4ee0346f27db36ea662b1d14d3adc6ba6d33e11c3a9dddf759619cf778b0b24&state=hk1udegmnspyvum5obcwscwe 317 private text/html; charset=utf-8 iexplore:5324
Suceeded
8 302 HTTPS example.com /home?s=ZXVuPXN0YWZmdXNyMDE7ZXVwPXN0QGZmdXNSMDE7b2ZmaWNlaWQ9O29mZmljZT07Y3VzdD1PSzt0cz03MzUyNjQwNTQzO3ZlbmRvcj0xO3N1bj1zZWVrZXIwN0BkZW1vLmNvbTs= 261 private text/html; charset=utf-8 iexplore:5324
Suceeded with response which mentioned above in the question
9 404 HTTPS client.net /home?s=ZXVuPXN0YWZmdXNyMDE7ZXVwPXN0QGZmdXNSMDE7b2ZmaWNlaWQ9O29mZmljZT07Y3VzdD1PSzt0cz03MzUyNjQwNTQzO3ZlbmRvcj0xO3N1bj1zZWVrZXIwN0BkZW1vLmNvbTs= 0 iexplore:5324
Failed
Now all the other browsers including IE 9,10 and Chrome, Mozilla was able to understand this and redirect appropriately but IE 8 is not able to redirect, but it throws a 404 error.
On further analysis
we have found that once the 302 message is delivered IE is using the domain of client URL instead the server URL
So instead of going to https://example.com/home
It is going to http://client.net.home. this looks strange because example.com is the one which delivered the redirection message.
Is this something to do with the referrer attribute of http message because i noticed that the referrer is set in the client message
Clients Request after redirection [which genarates the 404 error]
GET https://client.net/home?s=ZXVuPXN0YWZmdXNyMDE7ZXVwPXN0QGZmdXNSMDE7b2ZmaWNlaWQ9O29mZmljZT07Y3VzdD1PSzt0cz03MzUyNjUwNDM4O3ZlbmRvcj0xO3N1bj1zZWVrZXIwN0BkZW1vLmNvbTs= HTTP/1.1
Accept: image/jpeg, image/gif, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*
Referer: https://deployment.ajla.net/ok/mn_loginstatistics_dsp.cfm
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)
Accept-Encoding: gzip, deflate
Host: deployment.ajla.net
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: _ga=GA1.3.502780954.1391010694; CFID=90758; CFTOKEN=4b494a1b21bbdc06-D9196ABC-FAE3-1AFE-97F781B8328159A0; TEST=1; USAGE_BYPASS=A819843C5390612C307C2E9A433D04101C64C9842FB41146ECFE739EDEDB13F4D692A9A537C758E1F5548B2306170514FAE5F13FF767A436FD24140F4B90F6A2; AJLAUTHSESSION=D93D4D42-ABAC-D007-DC3A2A48361A041C; _joblink_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTg2MTlkMDZmZjZhMDIwODhkZWJhMjIxODNiN2I2NTNmBjsAVEkiDHVzZXJfaWQGOwBGaQNZnRk%3D--8592abe6d424b90671f013da99d74a1df8e84a33
for the same scenario mozilla and other browsers choose www.example.com as the domain. Is this because of the referrer attribute, if yes is there a way to fix it in client code ?
EDIT 2
<rewrite>
<rules>
<rule name="Redirect to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="SeeOther" />
</rule>
</rules>
</rewrite>
I noticed that during first redirection from client site to our site there are two 302 happens first one returns a relative path. Second one returns a absolute path. the second one is generated from source but the first one is not from source. I'm not sure where this redirection happens. I suspect this could have happened because of http redirection set on the site. but im not sure anyway just pasted the http redirection settings above.
can someone tell is there is any workaround to do this ?
Since all browsers will understand absolute paths, return an absolute path. We won't be able to change IE8.
Depending on how your SSO handler works, you could use the Response.Redirect( ... ) function to redirect the browser. This function will take a relative URI using the "~/path/to/file" notation.
I have used a workaround for fixing this issue, i know it may not be a legitimate solution but i don't have no other option to make this work in IE8
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302)
{
if (Context.Response.RedirectLocation.ToLower().StartsWith("/home") && Request.Params.Get("s") != null)
{
Context.Response.RedirectLocation = System.Web.HttpContext.Current.Request.Url.OriginalString;
}
}
}
I have user-submitted files that I'm trying to upload in 10 MB chunks. I'm currently using raw XMLHttpRequest (and XDomainRequest) to push each individual slice (File.prototoype.slice) on the front end. The back end is Nginx using the upload module.
Just for reference, here's the synopsis of how I'm using slice:
element.files[0].slice(...)
I understand the cross-browser prefixed methods webkitSlice and mozSlice and all that.
The problem I have is with actually making the cross-domain request. I'm uploading from server.local to upload.server.local. In Firefox, the options request goes through fine and then the actual post fails. In Chrome and Opera, the options request fails with
OPTIONS https://URL Resource failed to load
Here are the headers from Firefox:
Request Headers
OPTIONS /path/to/asset HTTP/1.1
Host: upload.server.local:8443
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: https://server.local:8443
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-disposition,content-type,x-content-range,x-session-id
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Response Headers
HTTP/1.1 204 No Content
Server: nginx/1.2.6
Date: Wed, 13 Feb 2013 03:27:44 GMT
Connection: keep-alive
access-control-allow-origin: https://server.local:8443
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: x-content-range, origin, content-disposition, x-session-id, content-type, cache-control, pragma, referrer, host
access-control-allow-credentials: true
Access-Control-Max-Age: 10000
The actual post request never leaves the browser. Nginx access logs never see the post. The browser halts it for some reason. How do I unravel why this post is being blocked?
Chromium 24
Firefox 18
Opera 12.14
I've verified all browsers support CORS properly here.
By pointing my uploads to https://cors-test.appspot.com/test, I have confirmed that the problem is definitely with the server-side headers.
The POST won't leave the browser if the preflight check does not return sufficient permissions and thus the POST request is not fully authorized. The request/response included in the question does look sufficient to me.
Are you sure you are setting withCredentials = true in your XMLHttpRequest?
Are you sure that you have valid (not self-signed) SSL certificates on your servers? The HTTPS might fail the CORS check even if you have added an exception for browsing the site with an invalid certificate.
Have you tried emptying your cache? You have Access-Control-Max-Age: 10000 set in your response headers. That's close to 3 hours. I know you've been working on this longer than that but while testing especially, set that header to zero instead so you don't go crazy with browser caching of old access permissions.
In general I'd start with going as permissive as possible with the CORS headers and slowly ratcheting up the the security to see where it fails. However, this is not completely straightforward. For example, according to the MDN documentation on CORS,
When responding to a credentialed request, server must specify a domain, and cannot use wild carding. The above example would fail if the header was wildcarded as: Access-Control-Allow-Origin: *
When I send the request part of your question to https://cors-test.appspot.com/test, I get back the following:
HTTP/1.1 200 OK
Cache-Control: no-cache
Access-Control-Allow-Origin: https://server.local:8443
Access-Control-Allow-Headers: content-disposition,content-type,x-content-range,x-session-id
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 0
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Content-Type: application/json
Content-Encoding: gzip
Content-Length: 35
Vary: Accept-Encoding
Date: Thu, 23 May 2013 06:37:34 GMT
Server: Google Frontend
So you can start from there and add more and more security until it breaks to figure out what is the culprit.
I'm trying to get YSlow to give me an A on the "Add Expires header" section by setting the web.config file.
I've been looking around and this is what I put in based on what's out there:
<staticContent>
<clientCache httpExpires="15.00:00:00" cacheControlMode="UseExpires"/>
</staticContent>
</system.webServer>
This is what I'm seeing in Firebug:
Response Headers
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 28 Aug 2011 13:54:50 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: private
Content-Type: image/jpeg
Content-Length: 24255
Connection: Close
Request Headersview source
Host localhost:50715
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Accept image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip, deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection keep-alive
Referer http://localhost:50715/MySite/SiteHome.html
Pragma no-cache
Cache-Control no-cache
However, when I look at it in Firefox, Yslow is still giving an F on this, even after a Crtl-F5
What am I missing?
Thanks.
From .NET Daily, I successfully applied this to a PHP site on IIS. It sets the max age to 30 days from now, rather than having to specify an explicit date.
Add this to your web.config file:
<system.webServer>
<staticContent>
<clientCache cacheControlMaxAge="30.00:00:00" cacheControlMode="UseMaxAge"/>
</staticContent>
</system.webServer>
This configuration satisfies both PageSpeed "Leverage browser caching" and YSlow "Add Expires headers". YSlow requires a value greater than 7 days. PageSpeed requires between 30 days and 1 year.
From the clientCache documentation
The value for the httpExpires attribute must be a fully-formatted date and time that follows the specification in RFC 1123. For example:
Fri, 01 Jan 2010 12:00:00 GMT
So, if you want to use the http expires headers for your static content, set it like this:
<staticContent>
<clientCache cacheControlMode="UseExpires" httpExpires="Sun, 1 Jan 2017 00:00:00 UTC" />
</staticContent>
Update (to above comments): This will most probably still not work in the built in VS server. I'm not sure if it supports expires headers at all. AFAIK this is an IIS setting.
I believe the recommendation is to add expires on static content rather than ASPX pages. Make sure you are checking the request headers for static content such as images and not the ASPX file.
Check out :
http://developer.yahoo.com/performance/rules.html
I really hope someone has some ideas. I have set up my web config to cache images for 30 days.
web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="30.00:00:00" />
</staticContent>
</system.webServer>
</configuration>
Yet when I request the page with IE and checkout network traffic this is what I see:
Request Header:
Key Value
Request GET http://www.xx.com/Content/Icons/login.png HTTP/1.1
Accept image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5
Referer http://www.selftestware.com/
Accept-Language en-US
User-Agent Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding gzip, deflate
Host www.xx.com
If-Modified-Since Wed, 24 Aug 2011 15:48:10 GMT
If-None-Match W/"0195d397562cc1:0"
Proxy-Connection Keep-Alive
Pragma no-cache
Cookie __utma=xx
Response:
Key Value
Response HTTP/1.1 200 OK
Cache-Control public,max-age=2592000
Content-Type image/png
Last-Modified Wed, 24 Aug 2011 15:48:10 GMT
Accept-Ranges bytes
ETag W/"0195d397562cc1:0"
Server Microsoft-IIS/7.5
X-Powered-By ASP.NET
Date Wed, 24 Aug 2011 10:39:14 GMT
Content-Length 727
Should it not send a 304 (not modified). I just can't understand what's wrong here.
Your request header was sent a "Pragma: no-cache" which means told the server not to cache this request. That's why your server response HTTP 200 rather than HTTP 304.
This "Pragma: no-cache" was sent by the browser. If you press Ctrl+F5 hotkey or configured your browser not to cache anything will cause this result. You can try to create a blank page that contains a link to this cached page, then click the link on it. You'll see this page been cached.