Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://.... This can be fixed by moving the resource to the same domain or enabling CORS.
There is good Wikipedia article on the subject which gives a global overview of the topic. An in depth guide is here. These two guides will be enough for you to get on, but depending on your environment it make take time to figure out all of the details.
Server sideServer side support boils down to this: Each and Every of your URLs has to support
OPTIONSmethod. This is the flow:
- Browser detects that you are targeting host different from the one your script was downloaded from
- Browser issues
OPTIONSrequest to the requests URL to find out if and what server allows to do with it. This is called "pre-flight" request and its cached by the browser
- If server's answer "satisfies" the browser, it proceeds with executing the original request you've asked for
So again, not only your main URL needs to support
OPTIONS, but every URL in your api. I.e.
/users/1, etc. Note, that accessing URL options should not require any authorization.
OPTIONS response needs include the following headers:
- Must have. It controls what domains can access your API. Note: not which clients (i.e. browsers), but it contains the list of domains, that if your script was downloaded from one of these domains, browser will allow it to request data from your site. If you are building general purpose API, you can just always put either
*there or echo back contents of request's
- Must have. List of methods you allow for this URL. Usually you'll list
GET, POST, OPTIONSfor collection URLs like
GET, PUT, DELETE, OPTIONSfor object URLs, i.e.
- Although not required but you'll need it in most cases. It controls which headers you allow the browser to include in request. For example, if you use Basic Auth, you need to put
Authorizationas a value of this header, otherwise you'll keep getting endless
Another good candidate is
Content-Typeheader - your browser will definitely want to send it when you are doing CRUD requests.
Multiple values can be comma (
,) separated. Header names are case-insensitive.
false(case matters!). Not setting this header is equivalent to saying
Access-Control-Allow-Credentials: false. So you can save some on web traffic here :)
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, PATCH, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization
And yeah, don't forget to include
OPTIONS itself in the list of allowed methods :)
Implementation detailsIn my environment, REST service sits behind Apache which acts as authentication proxy. Thus I had to split
OPTIONSresponse generation between Apache and my Python code. That is:
Access-Control-Allow-Originis set in Apache, because he is the gate keeper
Header set Access-Control-Allow-Origin "*"
Access-Control-Allow-Methodsis generated solely by Python code
Access-Control-Allow-Headersis set to
Content-Typeby Python. I did not want to add
Authorizationheader permission in Python, since authentication is done by Apache. So the following line additionally goes in Apache configuration
Header add Access-Control-Allow-Headers "Authorization"
Note that I'm doing "add" and not "set" in order not to overwrite values already existing in the header (from Python backend).
- The last trick is that
OPTIONSshould not be guarded by authentication. Apache's
LimitExceptdirective comes in handy:
AuthType Basic AuthName "My Realm" .... # Rest of the auth config <LimitExcept OPTIONS> Require valid-user </LimitExcept>
Security considerationsAfter first time reading about CORS I've asked myself: "All this CORS stuff is completely advisory! I've used my API from Python/requests for years without any CORS".
And yes, this is the case indeed. All of this CORS stuff if mainly to protect your from XSS. This is why CORS is blocked by default. To demonstrate the need for it:
- You go to your bank website, login, obtain session cookie
- Your bank webpage uses some JS code to poll the server for your account status. Browser automatically sends session cookie together with JS request, so the server lets you in
- Go to some other page that would contain JS code which connects to your bank server and attempts to transfer funds.
- Browser would happily attach your previous session cookie to the above request and boom - money got stolen. This is why CORS is disabled by default, so these kind of requests would be blocked.
The above scenario also outlines why its not enough to use just session cookie for authorization of CORS requests - that would be easy CSRF attack. In old, "forms world", we had csrf token being part of the form. This token should've match the csrf cookie on the
On the SPA side of things, there are no classic forms any more, so you need to either use Basic Auth, which is less efficient or session keys:
- When script logins, set him a session cookie and session key (some string that you can later match against session cookie).
- With each next request, require your scripts to send this session key as part of the request data, so you can verify it against session cookie
Hope this helps you and I wish you safe coding!