I have my express server hosted on Heroku, while my react app is hosted on Netlify.
My server has a login route, and whenever I attempt to log in from the client hosted on netlify, I get the below CORS policy error;
Access to XMLHttpRequest at 'https://u-annon.herokuapp.com/api/users/login' from origin 'https://boisterous-churros-56908e.netlify.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
There's no cors error on the GET routes that fetches post, and I don't get this error on localhost, even when I connect the client on the localhost to the server on heroku, the cors setting seems to handle that well, which makes me think this has something to do with netlify hosting. I've been reading about the netlify.toml file, but I can seem to find anything that fixes this problem.
Can someone point me in the right direction pls?
This is my cors setting on the express server;
app.use(cors({
origin: ["https://boisterous-churros-56908e.netlify.app", "http://localhost:3000"],
credentials: true,
methods: "GET, POST, PUT, DELETE",
preflightContinue: true,
allowedHeaders: ['Content-Type', 'Authorization']
}))
This is the login post request from the client
const LoginSubmit = (e) =>{
const data = {
email: email,
password: password
}
const login = axios.post(`${url}/users/login`, encodeURI({...data}))
.then((res) => {
if(res.status === 200){
parseJwt(res.data)
setLoggedIn(true)
setLoading(false)
navigate('/profile')
}
else{
setLoginError("An error occured, pls try again")
return
}
})
.catch((error) => {
console.log(error)
setLoginError("Email or password entered is incorrect, try again or signup")
});
return login
}
Related
I'm sending fetch from React to Express to authenticate with Google but I've been getting access blocked by CORS error. I'm redirecting the POST request from React to the Google URL for authentication. I tried using cors in the express app but I'm still getting the same error.
For fetching
const handleClick = (e) => {
fetch('http://localhost:8000/api/mail/login', {
method: 'POST'
})
.then(res => res.text())
}
Using cors in express app.js
app.use(cors())
Trying to redirect to google auth
const oauth2Client = new google.auth.OAuth2(
process.env.CLIENT_ID,
process.env.CLIENT_SECRET,
process.env.REDIRECT_URI
)
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: process.env.SCOPE
})
const gmail = google.gmail({
version: 'v1',
auth: oauth2Client
})
router.post('/login', (req, res, next) => {
res.redirect(url)
})
The error: Access to fetch at 'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fmail.google.com%2F&response_type=code&client_id=727520060136-ngpfd4ll798v42gfclh7cms9ndqstt32.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8000' (redirected from 'http://localhost:8000/api/mail/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The authentication flow must happen in a visible browsing context, not with a fetch request. In other words: You must navigate the current tab to (or open a new tab at) http://localhost:8000/api/mail/login, the tab will then be redirected to https://accounts.google.com/o/oauth2/v2/auth?... and this page becomes visible. Now the user must interact with that page to choose/confirm their Google account, after which they will be redirected to a page of your choice (for example, http://localhost:8000/welcome).
When made like this, none of the requests made is cross-origin, so no CORS will be involved at all.
Instead of the handleClick function, you need a login form like
<form action="http://localhost:8000/api/mail/login" method="post">
<input type="submit" value="Press to log in"/>
</form>
Front-End: [Axios]
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
console.log(res.data) // its okay, I can login if email & password are correct.
} catch (error) {
console.log(error)
}
}
Back-End [Nodejs ExpressJs]:
Inside App.js:
const cors = require('cors')
app.use(cors({ credentials: true }))
Inside Login.js (/auth/login endpoint):
// ... code, then... if email & password are correct:
// 3600000ms = 1hour
res.cookie('jwt', token, { httpOnly: true, expires: new Date(Date.now() + 3600000 })
res.status(200).json({
status: 'success'
token,
data: userDoc,
})
Then, when I login in my browser:
I can login successfully, but no cookies will be created, see:
The front-end http service (react app) is running on http://172.16.2.19:3000
The back-end http service (expressjs) is running on http://172.16.2.19:3001
The axios requests I'm sending from the front-end are requesting: http://172.16.2.19:3001
So what's the problem?
The problem that no cookies are getting created in the browser is preventing me from continuing to design the front-end application, because if I wanted to request anything from my API, I have to be authenticated, all the routes on the API I made are protected, so if I wanted to request anything from the API, I will have to send my jwt token along with the request.
edit **:
here's the response from the /auth/login endpoint after successfully logging in:
I am using brave browser, the latest version.
I tried this on firefox, it has the same behavior.
GUYS GUYS GUYS I found it!!!! after 3 hours of researching, let me save your time:
For anyone having the same problem, all you have to do is
change your backend code from:
const cors = require('cors')
app.use(cors({ credentials: true }))
to
app.use(cors({ credentials: true, origin: true }))
and make sure you're using withCredentials: true on the front-end with every request (the login POST method and all the other requests that requires authentication)
why?
setting origin property to true is going to reflect the request origin, the origin property can be a string if you wanted to specify a particular domain, ex: http://localhost:3000. But if you have more than one client, setting this to true is a wise choise.
and for those of you wondering about mobile devices in case of specifying a string for the origin field with one particular domain. This problem of cors only happens in browsers, any other client doesn't use that CORS policy.
I would check by passing {withCredentials: true} as the third argument to the axios method to allow the browser to set the cookie via the request.
I don't think it is correct to use the backend to save cookies, as cookies is a browser feature separate from the database. I might be wrong though. When the post is successful, res will return a token. You save this token in the browser's local storage.
const formSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const res = await axios.post('http://172.16.2.19:3001/api/v1/auth/login', {
email,
password,
})
//browsers local storage
localStorage.setItem('access_token',res.data.access);
localStorage.setItem('refresh_token',res.data.refresh);
console.log(res.data) // its okay, I can login if email & password are correct.
}
You will then have to create an authorization header as such
headers:{
Authorization: localStorage.getItem('access_token')
? 'JWT '+localStorage.getItem('access_token')
: null
}
I have two servers, frontend (Next.js) and backend (express.js api server).
Frontend server is running without any additions. But I have an nginx proxy for backend.
So far everything is good because they are not connected yet.
Frontend: is working as it should be.
Backend: I can make calls directly from the backend itself (by self origin).
When I make a fetch get call from my frontend server to the backend server, it normally gives a cors error because the origins are different.
For this, I set the backend server with cors:
// /src/middlewares/cors.ts
import cors from 'cors';
const whitelist = new Set(['http://192.168.1.106:3000', 'https://192.168.148.132']);
// frontend: http://192.168.1.106:3000
// backend: https://192.168.148.132
const corsOptions = {
optionsSuccessStatus: 200,
origin: (origin: any, callback: any) => {
console.log('origin: ' + origin);
if (whitelist.has(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
// credentials: true,
};
export default cors(corsOptions);
and
// /app.ts
import cors from './middlewares/system/cors.js';
.
.
// setup cors
app.options('*', cors);
app.use(cors);
.
.
After doing this, I reach my main goal. The frontend server can make call to the backend server.
output:
But this time I notice a problem. I can't send self request to backend anymore (by self origin).
When dealing with this I looked at the origins that came to the /src/middlewares/cors.ts file that I showed above.
for frontend:
for backend:
I am using self signed ssl in nginx for back server.
And there is not any cors relevant headers in conf.
How can i solve this situation?
(If I'm missing something, you can point it out in the comments.)
The Origin header is only set in cross-origin requests. If you call your backend directly, the Javascript value is undefined, and in this case you must not restrict anything. You could, for example, write
if (!origin || whitelist.has(origin)) {
callback(null, true);
}
Based on what i read and tested on basic javascript clients i can pass extra headers in my Angular Client which will then be avail on my NodeJs server in the
socket.handshake . But i am not sure what i am missing or what i am doing wrong since below code does not create
async socketInit() {
this.client = io(`${environment.socketUrl}`, {
transportOptions: {
polling: {
extraHeaders: {
Authorization: "Bearer authorization_token_here"
}
}
}
});
await new Promise((res) => {
this.client.on('connect', () => {
this.isConnected = true;
this.socketId = this.client.id;
this.client.on('disconnect', () => this.isConnected = false);
res();
});
})
.catch((e) => console.log(`Socket.io error: ${e.message}`));
}
for some reason this causes the connect to fail as i can see in my debug console of the angular that socket.io has a TransportError
In my case, I was trying to connect to a web-socket server on port 5001, while my angular app was opened on port 4200. When I added extraHeaders to the client options, the browser started to throw the CORS error like this:
origin has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The ports are not the same, and that's the cause of the cross-origin problem.
I've fixed it by setting my web-socket server port, the same as the web server, and opening the webpage on that port.
When sending POST or GET requests to our Express server which is being served through PM2 on EC2, Postman receives the full response including JSON data however our front end React app (both locally and deployed through CF) only receives the response status code and message and has the data variable as a blank string. The React app is using axios for these calls.
I've spent quite a bit of time with AWS support but we've concluded that it isn't an issue with EC2 or CF.
I've also gone over this a lot with my coworkers and we think it might be an async issue but we're stumped.
This is the client-side call:
export function signIn (data) {
return (dispatch) => {
dispatch(addSignInStatus('PENDING'))
axios.post(`${config.production_url}/api/user/login`, data)
.then(res => {
return dispatch(addSignIn(res.data))
}).catch(err => {
dispatch(addError(err))
return dispatch(addSignInStatus('ERROR'))
})
}
}
And this is the server-side response:
export function login(req,res){
let username = ''
let password = ''
if(req.body.username && req.body.password){
username = req.body.username;
password = req.body.password;
} else {
res.status(400).json({
message:"Error logging In",
error: "No username or password specified"
});
return;
}
_user.login(username,password).then(data=>{
res.status(200).json(data)
})
.catch(error=>{
res.status(400).json({message:"Error Signing in User",error:error});
});
}
In the React app, the server responds with the proper status code and message, but res.data is returned as a blank string and in Firefox this error is thrown:
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
The server should be responding with a JSON object similar to this:
{
"message": "Sign-in Successful",
"user": {
"skills": [],
"social": [],
"wallets": [],
"followers": [],
"following": [],
"groupsOwned": [],
"groupsJoined": [],
"starred": [],
"pinned": [],
...
},
...
}
and it does when the request is sent through Postman.
Edit:
The server does accept cross origin requests:
app.use(cors());
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Edit 2 SOLUTION:
Turns out it was an issue with the npm library "compression"
Looks like you're making cross-domain requests that are failing. Browsers block cross-domain requests by default. The concept of cross-domain requests doesn't apply to Postman, because your client (Postman) is running with all the network permissions of your OS, instead of being confined inside of a browser.
Check your "Network" tab in your browser's developer tools. If your requests are cross-domain, the browser will precede them with an OPTIONS request. This is known as a "preflight request".
At that point, if your server expects cross-domain traffic, it is supposed to respond with a set of headers authorizing cross-browser requests. If it does, the browser will be reassured that no one is doing anything shady, and it will proceed to make the POST request that's in your code.
If your server isn't expecting cross-domain traffic, it will not respond with these headers. The browser will then, according to its security model, block the POST request that you wrote in your code. It will look something like the blank request that you're describing.
Here's everything you need to know to implement CORS on your server:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS