I am using ReactJS and ExpressJS with jwt authenticate. In my server, I have config for cors like this
const corsOptions = {
//To allow requests from client
origin: true,
credentials: true,
methods: ['GET', 'PUT', 'POST', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'Set-Cookie'],
};
In my client, I send request to the Server like this
export function createNewRequest(data, callback) {
axios.post(`${process.env.REACT_APP_API}/api/requests`, data,
{
withCredentials: true
}).then(res => {
callback(res.data);
})
.catch(err => {
if (err.response) {
if (err.response.status === 403 || err.response.status === 401) {
getToken(createNewRequest(data, callback));
} else {
callback(err.response.data);
}
}
})
}
In my local environment, I test and every thing run fine. But when i deploy my server, then i try to send request from my local client to the server. The server doesn't receive cookies in headers.
I have tried setting origins like this
const corsOptions = {
//To allow requests from client
origin: ['http://localhost:3000'],
credentials: true,
methods: ['GET', 'PUT', 'POST', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'Set-Cookie'],
};
And tried to set default withCredentials
axios.defaults.withCredentials = true;
But none of these work. Can anyone explain to me what i did wrong and help me solve this
Try to allow app.use(cors()); like this without option(to allow everything).
example of code:
app.use(cors());
app.use(bodyParser.json({ origin: "https://famegoal.com/" }));
app.use("/function", couponRoutes);
mongoose
.connect(
`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASSWORD}#cluster0.qvs4c.mongodb.net/${process.env.DB_NAME}?retryWrites=true&w=majority`
)
.then(() => {
app.set("port", process.env.PORT || 5000);
app.listen(app.get("port"), function () {
console.log("Node app is running on port", app.get("port"));
});
})
.catch((err) => {
console.log(err);
});
That's not safe, but it's a great solution.
set the following middleware in your app.js as follows
app.use(function(req, res, next) {
res.header('Content-Type', 'application/json;charset=UTF-8')
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type,
Accept'
)
next()
});
and in reactjs use withCredentials: true
Related
I'm developing my final project and I'm trying to build a MERN project. I'm trying to implement an authentication with passport, and it works in development, but when I deployed I keep getting this error
Access to fetch at 'https://hospitalveterinariopeninsular.herokuapp.com/api/auth/googleLogin/success' 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.
But if I manually access the site: https://hospitalveterinariopeninsular.herokuapp.com/api/auth/googleLogin/success', I get the response I need. So I don't know if it's a problem of the server or from React.
This is my code in the server:
Index.js
const app = express();
app.use(
cookieSession({ name: "session", keys: ["whatever"], maxAge: 24 * 60 * 60 * 100 })
);
app.use(passport.initialize());
app.use(passport.session());
dbConnection();
// CORS
app.use(
cors({
origin: process.env.CLIENT_URL,
methods: "GET,POST,PUT,DELETE, PATCH",
credentials: true,
maxAge: 3600,
})
);
app.use(express.static(path.join(__dirname, "/public")));
app.use(express.json());
app.use("/api/auth", authRoutes);
AuthRoutes.js
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
// callback from google
router.get(
"/google/callback",
passport.authenticate("google", {
failureRedirect: "/api/auth/googleLogin/failed",
successRedirect: `${process.env.CLIENT_URL}/#/auth`,
})
// googleAuth
);
router.get("/googleLogin/success", (req, res) => {
console.log("success", req.user);
if (req.user) {
res.status(200).json({
success: true,
message: "successfull",
user: req.user,
token: req.user.token,
// cookies: req.cookies
});
}
});
Code from React
export const AuthPage = () => {
useEffect(() => {
const getUser = () => {
fetch(`${process.env.REACT_APP_API_URL}/auth/googleLogin/success`, {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
})
.then((response) => {
console.log("THIS IS THE RESPONSE", response);
if (response.status === 200) return response.json();
throw new Error("authentication has been failed!");
})
.then((resObject) => {
console.log("POR FAVOR********", resObject);
localStorage.setItem("token", resObject.token);
localStorage.setItem("token-init-date", new Date().getTime());
dispatch(startChecking());
})
.catch((err) => {
console.log(err);
});
};
getUser();
}, []);
return (...);
};
Edit to add github links
Backend: https://github.com/JavierGarciaGomez/hvp2021backend
Frontend: https://github.com/JavierGarciaGomez/hvp2021frontend
enviromental variables:
React:
REACT_APP_API_URL=https://hospitalveterinariopeninsular.herokuapp.com/api
Node:
PORT=4000
CLIENT_URL=http://localhost:3000
CLIENT_URL_PROD=https://www.hospitalveterinariopeninsular.com
The order is highly sensitive on express middlewares.
In your entrypoint you have this:
app.use(passport.initialize());
app.use(passport.session());
...
// CORS
app.use(
cors({
origin: process.env.CLIENT_URL,
methods: "GET,POST,PUT,DELETE, PATCH",
credentials: true,
maxAge: 3600,
})
);
Put the cors initialization before passport initialization:
// CORS
app.use(
cors({
origin: process.env.CLIENT_URL,
methods: "GET,POST,PUT,DELETE, PATCH",
credentials: true,
maxAge: 3600,
})
);
....
app.use(passport.initialize());
app.use(passport.session());
I think your passport routes are not being detected by your cors configuration because those where configured before the cors magic
The Setup
I have setup a frontend environment using create-react-app. In this environment I use Axios to make a POST request to my Node JS Express Backend Server /login endpoint. I setup sessions middleware using express-sessions and storing the sessions in Redis. I have all this running in localhost currently.
Environment
React App - http://localhost:3005/kp (Note: the service runs on
http://localhost:3005 however, all the routes have /kp in them)
Express Backend - http://localhost:5001
Redis - http://localhost:6379
What Works
When the frontend sends the request to the backend the Express server does it's thing and authenticates the user; it stores the username in the req.session.username (this is just as a test), Redis console shows that the key value store was successful, the frontend network tab shows a 200 status with a Set-Cookie response header and a tab that shows the cookie (screenshots below).
The Problem
Everything seems to be working fine but the cookie is not set in the Browser. I have refreshed the page, tried again many times and yet it will not set the cookie in any browser (Google Chrome & Safari). I am getting frustrated because it seems as though Chrome acknowledges that the Set-Cookie is present but ignores it for some reason.
What I've Tried
Axios
I have tried setting withCredentials: true - Does not work
Verified the cookie with Set-Cookie is being sent back to the frontend after the POST request
Backend
I have checked my CORS policies to but they seem fine; however, I am not great with CORS so there could be misconfiguration there
Tried setting credentials to true in CORS policy
Verified the session with variables are being set with Redis.
Code
React Frontend Axios POST Request
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
}
},
{ withCredentials: true }
)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
this.setState({
errorMessage: `Server Error`,
loading: false
});
});
Express Server
const express = require('express');
const http = require('http');
const cors = require('cors');
const socketServer = require('./src/sockets');
const bodyParser = require('body-parser');
const session = require('express-session');
const redis = require('redis');
const redisClient = redis.createClient();
const redisStore = require('connect-redis')(session);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const port = process.env.PORT || 5001;
const { loginRoutes } = require('./src/routers');
const app = express();
redisClient.on('error', (err) => {
console.log('Redis error: ', err);
});
app.use(cors({
origin: '*',
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
credentials: true
}));
// Redis session storage setup
// API Docs for express-session: https://www.npmjs.com/package/express-session
const sessionMiddleware = session({
secret: process.env.REDIS_SECRET || 'testing12345',
name: 'session',
resave: false,
saveUninitialized: true,
cookie: {
secure: false
},
store: new redisStore(
{
host: process.env.REDIS_HOSTNAME,
port: process.env.REDIS_PORT,
client: redisClient,
ttl: 604800
}
)
});
// Uses session middleware
app.use(sessionMiddleware);
app.use(bodyParser.json({ limit: '5mb' }));
const server = http.createServer(app);
// Starts Socket Server
socketServer(server, sessionMiddleware);
// Uses the routes from the router directory
app.use(loginRoutes);
server.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
Screenshots
Network Response
Request Cookie
Application Cookies
As you can see the cookie is missing in the list of browser cookies. I have a feeling it is something small but I have not been able to find anything.
I am not getting any errors in any service. Thank you in advance for your help.
Solution
As MichaĆ Lach pointed out I put withCredentials in the wrong place in the Axios call.
Frontend Axios
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
})
However, once I did this I began to get CORS error. The CORS error was you cannot have a wildcard '*' in your Access-Control-Allow-Origin (origin) configuration. For this example I changed it to point only to http://localhost:3005; however, there are ways to do dynamic whitelists as documented here: https://www.npmjs.com/package/cors#configuring-cors-w-dynamic-origin
Backend
app.use(cors({
origin: 'http://localhost:3005',
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
credentials: true
}));
Once I made these changes the cookie started being set on the frontend correctly.
Are you sure, you are setting axios options correctly ?
You have :
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
}
},
{ withCredentials: true }
)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
this.setState({
errorMessage: `Server Error`,
loading: false
});
});
Try this:
axios.post('http://localhost:5001/login', loginBody, {
headers: {
'Content-Type': 'application/json'
},
{ withCredentials: true }
}
....
I've been trying to get my frontend and backend to share cookies but the server never actually get them.
my frontend has credentials include on it
const res = await fetch('http://localhost:5000/v1/auth/register', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
and my backend have my core like
app.use(
cors({
origin: 'http://localhost:3000',
credentials: true,
})
);
and they still don't send cookies!
even tho I have a route that sets the cookie on the frontend from the backend
res.cookie('auth-token', token, {
httpOnly: true,
maxAge: 86400,
});
and it works perfectly fine after I used cors.
my check auth middleware:
exports.verifyAuth = async (req, res, next) => {
try {
const token = req.cookies['auth-token'];
if (!token) {
return res.status(401).json({ msg: 'No token, authorization required.' });
}
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
req.user = decodedToken.user;
next();
} catch (err) {
res.status(401).json({ msg: 'No token, authorization required.' });
}
};
EDIT:
I was debugging it and apparently its always the first ever time that it doesn't send the cookies and that's because I'm using getInitialProps to send the request but I'm guessing that the request happens before cookies are loaded so I'm trying to find a way to pass the cookies to the request manually because fetch doesnt.
Try this config: {withCredentials: true}
I have a React app, and an API. When i POST data to APIs login url API responses me back with cookie on successful login, which I have to set, so in each next request user will send this cookie. But I can't find a method to get it from response.
I want to set sessionid, but I can't reach it within code. I tried to do
Cookies.set('sessionid', response.headers['sessionid']);
But it sets undefined. console.log(response.headers) also gives me {content-length: "31", content-type: "application/json"}. Do I do something wrong?
Sender function:
formSender() {
const url_to_send = `${this.state.api_base_url}:${this.state.api_base_port}${this.state.api_user_url}/login/`;
axios.post(url_to_send, `username=${this.state.username}&password=${this.state.password}`, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})
.then((response) => {
// I need to set the cookie here
this.setState({
login_success: response.status === 200,
request_sent: false
});
})
};
Try to set Access-Control-Expose-Headers in the back end or
await axios({
method: 'post',
url: YOUR_URL,
data: Data,
headers: { 'Authorization': 'TOKEN' }
});
I have the same problems and i do that for resolve in backend:
app.use(cors({
origin: true,
credentials: true
}));
and the axios request :
axios({
method: "post",
url: `http://localhost:5500/api/user/login`,
withCredentials: true,
data: {
email,
password,
},
headers: {
"Content-Type": "application/json",
}
})
.then((res) => {
console.log(res);
})
I was initially looking for a solution to setting a cookie from a response, but I realized if it's passed as a Set-Cookie header then it is set by the browser. No need to set it manually. Here is the console view
My app looks something like this:
const app = express();
app.use(cors({
origin: ['http://localhost:3000'],
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD'],
credentials: true,
}))
app.use(cookieParser())
app.get('/foo', verifyToken, (req, res) => {
// you can omit verifyToken if you want, it's for bearer auth.
if (true) {
res.cookie('XSRF-TOKEN', 'example')
res.send('Welcome')
} else {
res.sendStatus(403);
}
});
The React side:
<Button onClick={() => {
axios.get('http://localhost:8081/foo', {
params: {},
headers: {
Authorization: `Bearer 123`,
// again, omit ^ if you're not doing bearer auth
},
withCredentials: true,
}
).then((response) => {
console.log('cookie should be set')
})
}}>Express cookie</Button>
Bear in mind if you're deploying to a server both react and express should be on an https connection. Connecting http <-> https causes other issues with cookies.
I have a frontend setup with react and a back-end made with express and mongodb, I have a component which needs to make a fetch request including the credentials with should be already set. All of the routes work on postman but I'm not able to recreate the functionality with the fetch function.
Express server:
...
server.use(helmet());
server.use(compression());
server.use(cors({
credentials: true,
}));
if (process.env.NODE_ENV !== "production") {
server.use(logger("dev"));
}
server.use(express.json());
server.use(express.urlencoded({ extended: false }));
server.use(cookieParser());
server.use(
session({
secret: process.env.COOKIE_SECRET,
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
})
);
server.use(auth.initialize);
server.use(auth.session);
server.use(auth.setUser);
//API ROUTES
server.use("/user", require("./api/routes/user"));
server.use("/pitch", require("./api/routes/pitch"));
server.use("/match", require("./api/routes/matchmaking"));
...
User routes:
router.post("/login", passport.authenticate("local"), (req, res, next) => {
return res.status(200).json({
message: "User logged in correctly",
redirect: "/"
});
});
router.get("/checklogin", (req, res, next) => {
if (req.user) return next();
else
return res.status(401).json({
error: "User not authenticated"
});
},
(req, res, next) => {
return res.status(200).json({
message: "User logged in correctly",
redirect: "/"
});
});
Frontend:
useEffect(() => {
async function fetchData() {
const response = await fetch("http://localhost:8000/user/checklogin", {
credentials: 'include'
});
const data = await response.json();
console.log(data);
}
fetchData();
}, []);
Using this code I get this error
Access to fetch at 'http://localhost:8000/user/checklogin' from origin 'http://localhost:3000' 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'.
As I said prior everything works on postman but not with the fetch function.
As the error says:
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'.
When you do this server.use(cors()), all of the requests are allowed by default and because of which, the 'Access-Control-Allow-Origin' header is set to '*'.
So, you might want to specify the corsOptions to get around this issue.
var whitelist = ['http://localhost:3000', /** other domains if any */ ]
var corsOptions = {
credentials: true,
origin: function(origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
server.use(cors(corsOptions));
You can do something like this
in express
app.use(cors({credentials: true, origin: 'http://localhost:3000'}));
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", 'http://localhost:3000');
res.header("Access-Control-Allow-Credentials", true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header("Access-Control-Allow-Headers", 'Origin,X-Requested-With,Content-Type,Accept,content-type,application/json');
next();
});
in router
router.all('*', cors());
while sending response do
res.header("Access-Control-Allow-Origin", 'http://localhost:3000');
res.json(someJson)