All requests blocked by CORS policy on development - ruby-on-rails

I have a Rails API with a React client side. I have had everything in the app setup for a long time now and today while I was working on it I suddenly started getting the error:
Access to XMLHttpRequest at 'http://localhost:3000/api/v1/user/authed'
from origin 'http://localhost:8000' has been blocked by CORS policy:
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'. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
Now none of the requests in my application work at all.
The request does go through from the React app to the Rails API and the Rails API responds properly as well (I can see this in the terminal) but nothing actually happens on the Client side because I am assuming it gets blocked for the CORS reason.
Is there something I can do to fix this? Could it be that some package is somehow updated on my system and different from the project so now it breaks?
URL to make request to:
const ENDPOINT = '/api/v1',
PORT = 3000,
URL = window.location.protocol + '//' + window.location.hostname + ':' + PORT + ENDPOINT;
The request
$.ajax({
url: URL + '/' + resource,
type: verb,
data: params,
xhrFields: { withCredentials: true }
})
.done(callback)
.fail(errcallback);
Request functions have the format:
static get(resource, params, callback, errcallback) {
API.send('GET', resource, params, callback, errcallback);
}

If your API doesn't require credentials you should remove withCredentials: true.
More about withCredentials:
The XMLHttpRequest.withCredentials property is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates. Setting withCredentials has no effect on same-site requests.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials

Related

Google Spreadsheet Formula Cell Copy (5th'nd) [duplicate]

Mod note: This question is about why XMLHttpRequest/fetch/etc. on the browser are subject to the Same Access Policy restrictions (you get errors mentioning CORB or CORS) while Postman is not. This question is not about how to fix a "No 'Access-Control-Allow-Origin'..." error. It's about why they happen.
Please stop posting:
CORS configurations for every language/framework under the sun. Instead find your relevant language/framework's question.
3rd party services that allow a request to circumvent CORS
Command line options for turning off CORS for various browsers
I am trying to do authorization using JavaScript by connecting to the RESTful API built-in Flask. However, when I make the request, I get the following error:
XMLHttpRequest cannot load http://myApiUrl/login.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'null' is therefore not allowed access.
I know that the API or remote resource must set the header, but why did it work when I made the request via the Chrome extension Postman?
This is the request code:
$.ajax({
type: 'POST',
dataType: 'text',
url: api,
username: 'user',
password: 'pass',
crossDomain: true,
xhrFields: {
withCredentials: true,
},
})
.done(function (data) {
console.log('done');
})
.fail(function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
alert(textStatus);
});
If I understood it right you are doing an XMLHttpRequest to a different domain than your page is on. So the browser is blocking it as it usually allows a request in the same origin for security reasons. You need to do something different when you want to do a cross-domain request.
When you are using Postman they are not restricted by this policy. Quoted from Cross-Origin XMLHttpRequest:
Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they're limited by the same origin policy. Extensions aren't so limited. An extension can talk to remote servers outside of its origin, as long as it first requests cross-origin permissions.
WARNING: Using Access-Control-Allow-Origin: * can make your API/website vulnerable to cross-site request forgery (CSRF) attacks. Make certain you understand the risks before using this code.
It's very simple to solve if you are using PHP. Just add the following script in the beginning of your PHP page which handles the request:
<?php header('Access-Control-Allow-Origin: *'); ?>
If you are using Node-red you have to allow CORS in the node-red/settings.js file by un-commenting the following lines:
// The following property can be used to configure cross-origin resource sharing
// in the HTTP nodes.
// See https://github.com/troygoode/node-cors#configuration-options for
// details on its contents. The following is a basic permissive set of options:
httpNodeCors: {
origin: "*",
methods: "GET,PUT,POST,DELETE"
},
If you are using Flask same as the question; you have first to install flask-cors
pip install -U flask-cors
Then include the Flask cors package in your application.
from flask_cors import CORS
A simple application will look like:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
#app.route("/")
def helloWorld():
return "Hello, cross-origin-world!"
For more details, you can check the Flask documentation.
Because
$.ajax({type: "POST" - calls OPTIONS
$.post( - calls POST
Both are different. Postman calls "POST" properly, but when we call it, it will be "OPTIONS".
For C# web services - Web API
Please add the following code in your web.config file under the <system.webServer> tag. This will work:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
Please make sure you are not doing any mistake in the Ajax call.
jQuery
$.ajax({
url: 'http://mysite.microsoft.sample.xyz.com/api/mycall',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
type: "POST", /* or type:"GET" or type:"PUT" */
dataType: "json",
data: {
},
success: function (result) {
console.log(result);
},
error: function () {
console.log("error");
}
});
Note: If you are looking for downloading content from a third-party website then this will not help you. You can try the following code, but not JavaScript.
System.Net.WebClient wc = new System.Net.WebClient();
string str = wc.DownloadString("http://mysite.microsoft.sample.xyz.com/api/mycall");
Deep
In the below investigation as API, I use http://example.com instead of http://myApiUrl/login from your question, because this first one working. I assume that your page is on http://my-site.local:8088.
NOTE: The API and your page have different domains!
The reason why you see different results is that Postman:
set header Host=example.com (your API)
NOT set header Origin
Postman actually not use your website url at all (you only type your API address into Postman) - he only send request to API, so he assume that website has same address as API (browser not assume this)
This is similar to browsers' way of sending requests when the site and API has the same domain (browsers also set the header item Referer=http://my-site.local:8088, however I don't see it in Postman). When Origin header is not set, usually servers allow such requests by default.
This is the standard way how Postman sends requests. But a browser sends requests differently when your site and API have different domains, and then CORS occurs and the browser automatically:
sets header Host=example.com (yours as API)
sets header Origin=http://my-site.local:8088 (your site)
(The header Referer has the same value as Origin). And now in Chrome's Console & Networks tab you will see:
When you have Host != Origin this is CORS, and when the server detects such a request, it usually blocks it by default.
Origin=null is set when you open HTML content from a local directory, and it sends a request. The same situation is when you send a request inside an <iframe>, like in the below snippet (but here the Host header is not set at all) - in general, everywhere the HTML specification says opaque origin, you can translate that to Origin=null. More information about this you can find here.
fetch('http://example.com/api', {method: 'POST'});
Look on chrome-console > network tab
If you do not use a simple CORS request, usually the browser automatically also sends an OPTIONS request before sending the main request - more information is here. The snippet below shows it:
fetch('http://example.com/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json'}
});
Look in chrome-console -> network tab to 'api' request.
This is the OPTIONS request (the server does not allow sending a POST request)
You can change the configuration of your server to allow CORS requests.
Here is an example configuration which turns on CORS on nginx (nginx.conf file) - be very careful with setting always/"$http_origin" for nginx and "*" for Apache - this will unblock CORS from any domain (in production instead of stars use your concrete page adres which consume your api)
location ~ ^/index\.php(/|$) {
...
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' "$http_origin"; # DO NOT remove THIS LINES (doubled with outside 'if' above)
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'My-First-Header,My-Second-Header,Authorization,Content-Type,Accept,Origin';
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=UTF-8';
return 204;
}
}
Here is an example configuration which turns on CORS on Apache (.htaccess file)
# ------------------------------------------------------------------------------
# | Cross-domain Ajax requests |
# ------------------------------------------------------------------------------
# Enable cross-origin Ajax requests.
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# http://enable-cors.org/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# Header set Header set Access-Control-Allow-Origin "*"
# Header always set Access-Control-Allow-Credentials "true"
Access-Control-Allow-Origin "http://your-page.com:80"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Allow-Headers "My-First-Header,My-Second-Header,Authorization, content-type, csrf-token"
Applying a CORS restriction is a security feature defined by a server and implemented by a browser.
The browser looks at the CORS policy of the server and respects it.
However, the Postman tool does not bother about the CORS policy of the server.
That is why the CORS error appears in the browser, but not in Postman.
The error you get is due to the CORS standard, which sets some restrictions on how JavaScript can perform ajax requests.
The CORS standard is a client-side standard, implemented in the browser. So it is the browser which prevent the call from completing and generates the error message - not the server.
Postman does not implement the CORS restrictions, which is why you don't see the same error when making the same call from Postman.
Why doesn't Postman implement CORS? CORS defines the restrictions relative to the origin (URL domain) of the page which initiates the request. But in Postman the requests doesn't originate from a page with an URL so CORS does not apply.
Solution & Issue Origins
You are making a XMLHttpRequest to different domains, example:
Domain one: some-domain.com
Domain Two: some-different-domain.com
This difference in domain names triggers CORS (Cross-Origin Resource Sharing) policy called SOP (Same-Origin Policy) that enforces the use of same domains (hence Origin) in Ajax, XMLHttpRequest and other HTTP requests.
Why did it work when I made the request via the Chrome extension
Postman?
A client (most Browsers and Development Tools) has a choice to enforce the Same-Origin Policy.
Most browsers enforce the policy of Same-Origin Policy to prevent issues related to CSRF (Cross-Site Request Forgery) attack.
Postman as a development tool chooses not to enforce SOP while some browsers enforce, this is why you can send requests via Postman that you cannot send with XMLHttpRequest via JS using the browser.
For browser testing purposes:
Windows - Run:
chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security
The command above will disable chrome web security. So for example if you work on a local project and encounter CORS policy issue when trying to make a request, you can skip this type of error with the above command. Basically it will open a new chrome session.
You might also get this error if your gateway timeout is too short and the resource you are accessing takes longer to process than the timeout. This may be the case for complex database queries etc. Thus, the above error code can be disguishing this problem. Just check if the error code is 504 instead of 404 as in Kamil's answer or something else. If it is 504, then increasing the gateway timeout might fix the problem.
In my case the CORS error could be removed by disabling the same origin policy (CORS) in the Internet Explorer browser, see How to disable same origin policy Internet Explorer. After doing this, it was a pure 504 error in the log.
To resolve this issue, write this line of code in your doGet() or doPost() function whichever you are using in backend
response.setHeader("Access-Control-Allow-Origin", "*");
Instead of "*" you can type in the website or API URL endpoint which is accessing the website else it will be public.
Your IP address is not whitelisted, so you are getting this error.
Ask the backend staff to whitelist your IP address for the service you are accessing.
Access-Control-Allow-Headers
For me I got this issue for different reason, the remote domain was added to origins the deployed app works perfectly except one end point I got this issue:
Origin https://mai-frontend.vercel.app is not allowed by Access-Control-Allow-Origin. Status code: 500
and
Fetch API cannot load https://sciigo.herokuapp.com/recommendations/recommendationsByUser/8f1bb29e-8ce6-4df2-b138-ffe53650dbab due to access control checks.
I discovered that my Heroku database table does not contains all the columns of my local table after updating Heroku database table everything worked well.
It works for me by applying this middleware in globally:
<?php
namespace App\Http\Middleware;
use Closure;
class Cors {
public function handle($request, Closure $next) {
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', "Accept,authorization,Authorization, Content-Type");
}
}

Reddit API returns HTTP 403

Following the OAuth2 login flow described at https://github.com/reddit/reddit/wiki/OAuth2 I got to the point where POST-ing to https://www.reddit.com/api/v1/access_token returns something like this:
{'token_type': 'bearer', 'expires_in': 3600, 'scope': 'identity', 'access_token': '*****'}
Then I do
GET https://oauth.reddit.com/api/v1/me
With this header:
Authorization: bearer *****
The response is HTTP 403 Unauthorized. But why? It is clear that the access token has 'identity' scope. It is also documented that the /api/v1/me call requires this scope only. (See https://www.reddit.com/dev/api/oauth#GET_api_v1_me )
So why am I getting http 403?
I was experiencing the exact same issue as you described. In my case, I resolved the 403 by adding a faux user agent string in the request headers.
In my case, using HttpClient of C#, this proceeds like so:
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("User-Agent", "MockClient/0.1 by Me");
...
}
In my case this was because of a redirect.
Calling a non-oauth endpoint in my application (like https://www.reddit.com/r/learnpython/about.json) with the Authorization header would fail with code 403. But calling https://reddit.com/r/learnpython/about.json (without www) with the Authorization header succeeded. However both endpoints worked when I tried via Postman.
The reason for this is that reddit.com would redirect to www.reddit.com, which results in the Authorization header being dropped by Postman for the second request. In my application code, I was including the header with both requests, which explains the different behavior.
Solution: don't include the Authorization header when calling non-oauth endpoints.

Paw not finding access_token from OAuth proxy

I have a use-case where I need to spoof a white-listed Redirect URL locally when performing OAuth 2 authentication.
I'm running a very basic web-server coupled with a hosts file entry for the domain I'm spoofing. I'm able to correctly negotiate my tokens and return them to Paw, but Paw isn't picking up my access_token or refresh_token, it simply displays the raw response:
Here's my server code (with placeholders for sensitive data):
var http = require('http'),
request = require('request');
var PORT = 6109;
var server = http.createServer(function(req, res) {
var code = req.url.split('?')[1].split('=')[2];
request({
url: 'https://<access token URL>/oauth2/token?code=' + code,
method: 'POST',
form: {
'client_id': <client_id>,
'client_secret': <client_secret>,
'grant_type': 'authorization_code',
'redirect_uri': <spoofed redirect URL>
}
}, function(err, response, data) {
data = JSON.parse(data);
res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify(data.result));
// I also tried this with the same end-result
// res.writeHead(200);
// res.write('access_token=' + data.result.access_token + '&token_type=' + data.result.token_type + '&refresh_token=' + data.result.refresh_token);
res.end();
});
});
server.listen(PORT, function() {
console.log('Server listening on port %d', PORT);
});
What am I missing? Why isn't Paw finding my tokens?
Here's my configuration for reference:
Some other noteworthy points:
The OAuth provider is non-standard and flubs quite a few things from the spec (my proxy exists in part to patch up the non-standard bits)
The domain for the Redirect URL is real, but the URL does not resolve (this is a part of the reason for the local hosts entry)
I'm not showing this part of the flow, but I am correctly completing the authorization step prior to being given the code value
I think you're probably confused between the Authorization URL and Access Token URL. When you're in Authorization Code grant type for OAuth 2, you're expected to have a user confirmation step in a web page (the Authorization URL).
Which makes me guess that instead, you're expecting instead to use the Password Grant or Client Credentials? Otherwise, if you want to use Authorization URL, you'll need to specify a webpage at the Authorization URL.
Note: I've tried your Node.js script in Paw using the two last grants I mentioned (Password Grant & Client Credentials), and it works nicely.
Update: Following the comments below, I understand more what you are doing. The Authorization Request should (if successful) return a 302 redirect response to the Redirect URL page, and append a code URL query param to it. It seems like you're returning a JSON response with the code instead, so Paw isn't catching it.
According to the OAuth 2.0 spec (RFC 6749), section *4.1.2. Authorization Response*, if granted, the code should be passed as a URL query param (i.e. a ?key=value param in the URL) to the Redirect URL when doing the redirection.
If the resource owner grants the access request, the authorization
server issues an authorization code and delivers it to the client by
adding the following parameters to the query component of the
redirection URI using the "application/x-www-form-urlencoded" format
Quoting the example from the spec, here's how the response of the Authorization Request should look like if it's a success (code is granted):
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
I saw that the Redirect URL contains "my Spoofed Uri".
When we need to use authorization code flow, we provide the authorization code and redirect Uri.
When the URI you are providing does not match the URI saved for the client in Identity server, you will not be able to get the token as the URI does not match with the client authorization code.
For example : Consider client identity in the Identity server be:
Auth Code: "xyx"
Redirect Uri: "www.mylocalhost.com\xyz"
And in your example the combination you are providing is:
Auth Code: "xyx"
Redirect Uri: "<my spoofed uri>"
As these 2 wont match there will be no token received.
I believe if you use the correct URI that is registered with the client in the Identity server, you will be able to receive the token.

Request option "withCredentials" - Angular2

I am getting a CORS issue using request to connect to Neo4j in an Angular2 component:
Response to preflight request doesn't pass access control check. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'http://localhost:8080' is therefore not allowed access. The credentials mode of an XMLHttpRequest is controlled by the withCredentials attribute
How do I set withCredentials: false using request (Typescript)? I'm assuming this will fix the problem. But the request ts file does not list withCredentials in its CoreOptions object. Neither does the Neo4j-Typescript package include this in its Typescript definition.
You can do this by extending the BrowserXhr class:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor() {}
build(): any {
let xhr = super.build();
xhr.withCredentials = false;
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
I had the same issue in my Angular2 application.
The problem is that before every request made by the client a preflight request is sent to the server.
This kind of request have a type OPTIONS, and it's duty of the server to send back a preflight response with status 200 and headers set for accepting requests from that client.
This is my solution (with express):
// Domain you wish to allow
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'YOUR-CUSTOM-HEADERS-HERE');
// Set to true if you need the website to include cookies in requests
res.setHeader('Access-Control-Allow-Credentials', true);
// Check if Preflight Request
if (req.method === 'OPTIONS') {
res.status(200);
res.end();
}
else {
// Pass to next layer of middleware
next();
}
As you can see, i set the headers and then fetch if the request type is OPTIONS. In that case i send back a status 200 and end the response.
In this way, the client will be authorized and you will be also able to set your custom headers in all the requests without worrying about CORS anymore.
If you really want to change withCredentials than you have to provide customized BrowserXhr, see this answer.

Connect and pass Auth token to a SignalR hub with CORS enabled

I have three ASP.NET WebAPI endpoints:
Identity server, which generates bearer tokens (serverA.com);
SignalR server with hub (serverB.com);
Some endpoint with a simple ASP.NET MVC view and SignalR JS client script (serverC.com).
All three servers use OAuth2 middleware as Auth mechanism. Microsoft.Owin.Cors is configured as well.
Servers use only HTTPS requests.
SignalR v2.2.0 is installed on serverB.com.
I can successfully make a cross-domain request from serverC.com to serverA.com to get bearer token, but, actually, I don't know how to pass auth token while connecting to serverB.com
There are two ways I found so far:
Pass auth token as a query string (not secure);
Apply this setting to the jQuery.ajax
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader('tokenKey', 'tokenValue');
}});
but it forces SignalR to use long polling only.
Is there any other way to send auth token (not in query string) so it can be consumed and validated by OAauth BearerAuthorizationProvider? Maybe, cookies, headers or any other way?
Update
CORS middleware set up for both environments to allow all data and accept credentials.
Here is my OWIN middleware:
var requestUri = context.Request.Uri.AbsolutePath;
if (string.Equals(requestUri, authRoute, StringComparison.OrdinalIgnoreCase))
{
if (!context.Request.Headers.ContainsKey("Authorization") || string.IsNullOrEmpty(context.Request.Headers["Authorization"]))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
context.Response.Cookies.Append("BearerToken", context.Request.Headers["Authorization"]);
}
}
else
{
await Next.Invoke(context);
}
Then I do first Ajax request:
$.ajax({
url: self.communicationHubUrl + '/authenticate',
type: 'post',
cache: false,
crossDomain: true,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', self.accessToken);
});
middleware sets auth token from header to the response cookies.
Then I call hub.Start so SignalR begins to send ajax requests.
But for some reasons I don't quite understand, cookies are present in request only if I enable xhr.withCredentials = true for ALL ajax requests via $.ajaxSetup
$.ajaxSetup({
xhrFields: {
withCredentials: true
}
});
Without this setting request doesn't include cookies. On the other hand, I guess it's not a good decision to force all ajax requests to enable such settings.
Furthermore, I've faced strange behavior in Oauth middleware: ValidateIdentity method is not invoked when request from signalR comes so instead of 401 Unauthorized, I'm getting deafult principal.
I think that putting the auth token insied a cookie will be your best bet. Unlike the ajaxSetup option, cookies are sent with EventSource and WebSocket requests.
You might need to add some middleware to the SignalR server (serverB.com) that sets the appropriate cookie when you POST the auth token before starting the SignalR connection.

Resources