CORS: proxying localhost via webpack-dev-server to external API - reactjs

I'm building simple react-powered contact page and it would be swell if I could test email sending from my local development machine. I'm using Zoho Email API, which I tested with curl and Insomnia - there everything is fine, I'm able to send emails.
Problems start when I try to do the same from React app via axios. The app itself is served locally via webpack-dev-server. Without proxying request through wds, the browser complains about CORS. But when I'm trying to proxy request via wds with the appropriate headers set I'm being greeted with 400 Bad Request and not much more to aid with the debugging.
The relevant wds setup:
const ReactRefreshWebpackPlugin = require('#pmmmwh/react-refresh-webpack-plugin')
exports.devServer = ({ host, port } = {}) => {
const plugin = new ReactRefreshWebpackPlugin()
return {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers':
'X-Requested-With, content-type, Authorization',
},
proxy: {
'/api/accounts/ZOHO_ID_NOT_SHOWN/messages': {
target: 'https://mail.zoho.eu',
secure: false,
},
},
https: true,
contentBase: path.join(__dirname, 'assets'),
stats: 'errors-only',
historyApiFallback: true,
overlay: true,
hot: true,
host,
port,
},
plugins: [plugin],
}
}
And function in which I'm using to axios to send the email:
const sendEmail = async () => {
const { name, email, message } = formState
try {
const response = await axios({
method: 'post',
url: EMAIL_SEND,
headers: { Authorization: EMAIL_AUTH },
data: {
toAddress: EMAIL_TO,
subject: `Message from ${name} # ${email}`,
content: message,
},
})
console.log(response) // debugging
} catch (e) {
console.log(e.response)
}
}
In the above function, EMAIL_SEND is imported from elsewhere and its value is /api/accounts/ZOHO_ID_NOT_SHOWN/messages - mirroring the wds proxy value.
With the above setup, when I try to send a test email from localhost I'm getting 400 Bad Request... and not much more. I'm sure that the issue is with how I'm proxying the request via wds but I kind of run out of ideas how I can fix it. Any suggestions will be greatly appreciated!

Related

CORS issue: Next.js application

I am trying to deploy a Next.js application with Dokku (Heroku). The application was previously deployed to Vercel without error, but on the Dokku deploy CORS is failing. I've made some progress in fixing it.
The Next.js server communicates with another eternal Python Django API through an API gateway.
Initially the POST request errored with the "No 'Access-Control-Allow-Origin' header is present" error. I added the headers to moduleExports:
next.config.js
const moduleExports = {
async headers() {
return [
{
source: "/api/(.*)",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }
]
}
]
},
async redirects() {
return [
{
source: '/account',
destination: '/account/profile',
permanent: true,
},
]
},
};
Thereafter I began receiving a new error that the preflight options request did not return 200. I added a check for options requests to my handler:
pages/api/sign-up.js
export default async function handler(req, res) {
if (req.method === 'OPTIONS') {
res.status(200).end();
}
const { email, password1, password2, first_name, last_name } = await req.body
const response = await fetch(REGISTRATION_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( { email, password1, password2, first_name, last_name } ),
});
const data = await response.json()
res.status(200).json(data)
}
At this point, interestingly, the request does make it to the gateway and is accepted and indeed the new user is successfully created at the Django API. However the communication between the next.js server and client still shows a CORS error and the page does not update to show success. The CORS error is back to the first one "No 'Access-Control-Allow-Origin' header is present on the requested resource". The difference of course is that earlier the user was not created on the Django end.
My question of course is how can I fix this since I'm out of ideas or things to try now.
There is your problem:
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
// ...
]
You cannot use the wildcard (*) in conjunction with credentialed requests. As explained in the section entitled Credentialed requests and wildcards of the MDN Web Docs about CORS:
When responding to a credentialed request, the server must not specify the "*" wildcard for the Access-Control-Allow-Origin response-header value, but must instead specify an explicit origin; for example: Access-Control-Allow-Origin: https://example.com.
Accordingly, instead of using the wildcard, you should specify the allowed origin(s) explicitly:
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "https://yourfrontendorigin.com" },
// ...
]

Why does a fetch call work on port 3000 when I'm serving from port 8000?

I am working through a React tutorial which has the following fetch call:
const upvoteArticle = async () => {
const result = await fetch(`/api/articles/${articleName}/upvote`, {
method: 'post'
});
console.log(result);
const body = await result.json();
setArticleInfo(body);
}
The code works, i.e. updates my vote counter in my database fine, but I notice when looking at the url that it is using port 3000 while I am serving my API on port 8000:
Response { type: "basic", url: "http://localhost:3000/api/articles/learn-react/upvote", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: false }
I notice also in Postman, that both of these URLs work:
localhost:3000/api/articles/learn-react/upvote
localhost:8000/api/articles/learn-react/upvote
Where is the port being remapped to 3000, and what is the sense of this? For instance, if I wanted to have two APIs running locally, e.g. one on port 8000 and one on port 8001, how would I specify this?

How to send request to an API on remote server via webpack devlopment server using axios

I want to fetch some data from my remote server supporting REST API.
I am using axios and web-dev-server. My frontend is sending request and I have used both firefox and chrome to open my frontend.
However every time I tries to make request I encounter cors error.
Also I don't want to make any changes on my server.
The firefox and chrome are sending this header.
Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language :en-US,en;q=0.5
Connection:keep-alive
Host:my.ip.to.host:port
Origin:http://localhost:3000
Referer:http://localhost:3000/login
User-Agent:Mozilla/5.0 (X11; Ubuntu; Linu…) Gecko/20100101 Firefox/67.0
I have tried to run my simple request code on an online platform without web-dev-server and there it runs perfectly fine.
Here is my code
//********** my request*********
return axios
.get('http://my.ip.to.host:port/api/User/login', {
headers: {
Accept: '/'
}
})
.then(function(response) {
console.log(response);
return 'user';
})
.catch(function(error) {
console.log(error);
return 'err';
});
//*****************************
//*****webpack.config.js********
var HtmlWebpackPlugin = require('html-webpack-plugin');
require('babel-polyfill');
module.exports = {
mode: 'development',
entry: [ 'babel-polyfill', './src' ],
resolve: {
extensions: [ '.js', '.jsx' ]
},
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
devServer: {
historyApiFallback: true,
port: 3000
},
externals: {
// global app config object
config: JSON.stringify({
apiUrl: 'http://localhost:4000',
checkLogin: 'http://my.ip.to.host:port/api/User/login'
})
}
};
Here is the error I am getting.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://my.ip.to.host:port/api/User/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).`enter code here`
You need to set withCredentials as true in axios config. Cors Setting
let HttpInterceptor = axios.create({
headers: {
Accept: "*/*"
}
});
HttpInterceptor.interceptors.request.use(
function(config) {
config.withCredentials = true; // To enable CORS
return config;
},
function(error) {
return promise.reject(error);
}
);
//********** Your request*********
return HttpInterceptor
.get('http://my.ip.to.host:port/api/User/login')
.then(function(response) {
console.log(response);
return 'user';
})
.catch(function(error) {
console.log(error);
return 'err';
});
Here Google has explained the cors(Cross-origin requests) very nicely.
I have worked around this by hosting a proxy server(on the same local server where I am hosting my client) and redirecting all my single page app request via that.
First of all, I created a proxy setting in devsever key of webpack config file, like this.
devServer: {
proxy: {
//abc is REST API request endpoint on my backend
'/abc': {
//target is where your proxy server is hosted
target: 'http://localhost:5000',
secure: 'false'
},
//xyz is REST API request endpoint on my backend
'/xyz': {
//target is where your proxy server is hosted
target: 'http://localhost:5000',
secure: 'false'
}
},......// rest of the setting
}
Then,
For a particular invocation of a action via my client I make request to my backend like this.
axios
.get('/startAppkey', { withCredentials: true })// withCredential enables passing of cookies and session id. If you dont want to creat session you can avoid this.
.then((response) => {
// do stuff with response
})
.catch(function() {
//do stuff
});
Our client is all set.
Now time for proxy server.
First install http-proxy-middleware,Like this.
sudo npm i --save http-proxy-middleware
//it is also avilable on yarn
then,
To setup proxy server here is few lines of code.
import * as express from 'express'; // using express to support my middleware
import * as proxy from 'http-proxy-middleware';
const app = express();
// the path /abc and /xyz should be same as you have declared in in webpack config file
app.use('/abc', proxy({ target: 'http://your/backend/api/endpoint'}));
app.use('/xyz', proxy({ target: 'http://your/backend/api/endpoint'}));
//that's it you are done here.

CORS error on request, with headers and webpack-dev-server proxy configured

I have a simple ReactJS+Typescript application (bundled with Webpack) that I've been designing for the last couple of days. Now, I was starting the backend connection part (not my field, nor my responsability in this context), with which I'm having some difficulties. The main problem is a CORS error on the browser. This is my setup:
"webpack": "^4.30.0",
"webpack-dev-server": "^3.3.1",
"typescript": "^3.4.5",
"react": "^16.8.6",
"axios": "^0.18.0",
Chrome version 74.0.3729.131 (64-bit)
Without any type of suggestion or experiment, this is the error I encountered:
I've tried a couple of things to solve this problem, but none of them actually fixed it. They are as follows:
On my webpack.dev.ts configuration file, I configured the webpack proxy property. I configured it like this:
devServer: {
contentBase: path.join(__dirname, process.env.PUBLIC_PATH || '../dist'),
compress: true,
host: process.env.DEV_HOST || 'localhost',
port: process.env.DEV_PORT || 8000,
open: 'chrome',
proxy: {
'/api': {
target: 'http://<ip>:8888',
secure: false,
},
},
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': '*',
},
},
The request to the backend is built as follows:
const config = {
method: 'post',
url: '/api/login',
data: { password: password },
};
axios(config)
.then(resp => console.log(resp))
.catch(error => console.log(error));
Please note that, for now, the password field is just a placeholder and is not validated on the backend, and as such, the Access-Control-Allow-Credentials header is not required.
To my big disappointment, the request doesn't seem to be using the proxy configured, since this is the error:
On the network tab, there's also no reference to the backend IP address. Please note that only in this case does the request show as a POST and not as an OPTIONS request.
Tried configuring axios with the headers required for CORS control on my side (client). This also didn't bring any solution to the table. This is what I came up with:
const config = {
method: 'post',
url: 'http://<ip>:8888/api/login',
data: { password: password },
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': '*',
},
};
axios(config)
.then(resp => console.log(resp))
.catch(error => console.log(error));
The result that appeared is similar to the one without any configuration (first image attached here).
Finally, I installed a Chrome extension for CORS, that (I suppose) attaches the Access-Control-Allow-Origin: * header to all requests. This is where things get interesting/weird. With this extension active (and without configuring it), even if I disable all the other previous options, the error changes:
I've also tried activating all three previous options at the same time, and this last error is what appears.
I'm not sure where to head next, or how to proceed from here. I'm avoiding requesting a change to the server API's headers, but I'm keeping that option open as a last resort.
I've tried to be thorough with the explanation and with what I want to achieve, but feel free to request more information.
Thanks in advance.
After all this, I had no choice but to update the backend with a couple of headers when receiving a preflighted (OPTIONS) request:
Access-Control-Allow-Headers, which contains whatever the request has on that same header;
Access-Control-Allow-Origin, which is set to *. Since in production, both the frontend and the backend will be local, this will only be used for our development environment;
Access-Control-Allow-Methods, which is set to * by convenience. We could specify which methods would be permitted on our allowed domains, but we thought this wasn't necessary for a development env.
After setting these flags, I left the proxy configuration set on Webpack, as such:
proxy: {
'/api': {
target: 'http://<ip>:8888',
secure: false,
},
},
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': '*',
},
On re-testing our requests and everything went smoothly. We found no way of doing this solely on the client side.
Thanks for the comments, everyone.
Exposing an API backend server from the same origin the web application is loaded helps to get around the CORS restriction. It's where the webpack dev-server proxy comes in handy. First, make sure requests to the API address the proxy. If XMLHttpRequest's or fetches in source code address absolute URLs (as opposed to relative ones) use a flag that indicates a development mode to choose the proxy as the origin. For example:
fetch(isDevelopment ? '/api' : 'http://backend.server.com/api')
.then(response => processResponse(response));
Second, configure the proxy to target the required API on the real backend server:
devServer: {
proxy: {
'/api': {
target: 'http://backend.server.com'
}
}
}
There are no cross-origin requests from a browser's viewpoint anymore. This configuration may be also enough for the server to respond properly (for instance Jetty 9 with its CORS filter set works for me just fine). On the other hand, browsers may send Origin headers even with same-origin requests. To prevent CORS issues on different backend servers, if there is any, change the Origin header to match the target URL like this:
devServer: {
proxy: {
'/api': {
target: 'http://backend.server.com',
onProxyReq: proxyReq => {
if (proxyReq.getHeader('origin')) {
proxyReq.setHeader('origin', 'http://backend.server.com');
}
}
}
}
}
Refer to the documentation of dev-server and the http-proxy-middleware library it uses under the hood for more information. Also, TCP/HTTP sniffers like Wireshark are very helpful to understand what is sent where and what is sent in response.
Since no access-control-allow-origin header is present in response header, you can force one in your proxy settings:
proxy: {
'/api': {
target: 'http://<ip>:8888',
secure: false,
onProxyRes: response => {
response.headers['access-control-allow-origin'] = 'http://localhost:8000';
},
},
},
It means that all proxied responses will have this header. This onProxyRes is http-proxy-middleware stuff, source: https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/proxy-events.md
Set the changeOrigin to true. See links below.
proxy: {
'/api': {
target: 'http://<ip>:8888',
secure: false,
changeOrigin: true,
},
},
https://webpack.js.org/configuration/dev-server/#devserverproxy
https://github.com/chimurai/http-proxy-middleware

Webpack dev server Proxy is not working - ReactJS

I want run my project on localhost:3000/upct/ROUTES
But I have my API in: http://desarrollo.com/api
I wanna use proxy option in Webpack but it is not working. I get error of CORS and others... My proxy config looks like:
CONFIG.devServer = {
//host: 'localhost',
port: 3000,
proxy: {
'/api/**': {
target: 'http://desarrollo.com/api',
secure: false,
changeOrigin: true
}
},
contentBase: PATH.join(__dirname, '/src'),
hot: true,
inline: true,
historyApiFallback: true/*,
headers: {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}*/
};
I do my AJAX querys like:
$.ajax({
url: "http://desarrollo.com/api",
data: "",
type:"GET",
dataType: "JSON",
})
.done((respuesta) => {
console.log(respuesta);
}).fail(function(xhr, textStatus, errorThrown){
console.log("XHR: ", xhr/*.responseText*/, "Text Status: ", textStatus + '\n' + "Error Thrown: ", errorThrown);
})
I supposse proxy is for doing AJAX querys into my API without CORS errors. But it is not working. What is wrong here?
Thank you.
When using proxy, you have to send your requests to the localhost, so that proxy could redirect them to remote server without CORS. In your $.ajax() pass url: "/api".
After that when you run your app locally your requests will be sent to http://localhost:3000/api and when it runs on http://desarrollo.com it will send requests to http://desarrollo.com/api.
To add more to #GProst response, Below changes will work.
Find Protocol :
this.protocol = () => {
let returnValue = 'https://';
if (location.protocol !== 'https:') {
returnValue = 'http://';
}
return returnValue;
};
Find Hostname :
const hostName = window.location.hostname + (!window.location.port === false ? ':' + window.location.port : '');
Required URL :
const URL = this.protocol() + hostName;
Now Above URL can be used in Ajax.
$.ajax({
url: URL,
data: "",
type:"GET",
dataType: "JSON",
})
This will work in both webpack-dev-server as well as in application server e.g. Apache.

Resources