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)
Related
I've followed a guide on Medium on the implementation of Passport js with a MERN stack and whilst I got authentication to work, I'm struggling to persist users between routes. Below are snippets of my code:
Backend
Server.js setup (part of it):
const mongoose = require('mongoose')
const MongoStore = require('connect-mongo')
const passport = require('./passport/setup')
const MONGO_URI = 'db'
const app = express();
mongoose
.connect(MONGO_URI, { useNewUrlParser: true })
.then(console.log(`MongoDB connected ${MONGO_URI}`))
.catch(err => console.log(err))
app.set("")
const PORT = process.env.PORT || 5000;
if (PORT !== 5000) {
app.use(enforce.HTTPS({ trustProtoHeader: true }));
}
app.use(cors({
origin: true,
credentials: true,
}))
app.options('*', cors())
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
Passport.js Local Strategy setup:
const bcrypt = require('bcryptjs');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User')
passport.serializeUser((user, done) => {
done(null, user.id);
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(new LocalStrategy({ usernameField: "email" }, (email, password, done) => {
User.findOne({ email: email })
.then(user => {
if (!user) {
return done(null, false, { message: 'No User Found' })
} else {
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: "Wrong Password" });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
Login route:
router.post("/new-login", async (req, res, next) => {
passport.authenticate("local", function (err, user, info) {
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function (err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
})
Frontend React:
Posting Login Data:
export const postData = async (url, data) => {
try {
console.log('posting this ', data)
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.post(url, data, config);
console.log('getting this', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
console.log('Error', exp)
return {
success: false,
error: exp.response.data,
};
}
};
Axios call:
let response = await postData('/login/new-login', { email, password })
When trying to login all of this works and returns the login success message with user.id from the login route, but when I look at the Axios response there's no reference to any user or session as shown below:
I'm probably not understanding how users are passed to React from the backend but shouldn't res.user exist here following req.LogIn in Passport? Or should I take the user.id and save it in a global React state and attach it every time I do a request to a protected route? Right now if I do a GET request to a protected route I get a req.user undefined message.
Frontend Axios call:
export const getData = async (url) => {
console.log('not called?')
try {
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.get(url, config);
console.log('response? ', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
return {
success: false,
error: exp.response.data,
}
}
}
Backend Protected Route:
router.get("/new-protected", passport.authorize('local', { failureRedirect: '/login' }), (req, res) => {
console.log('whats request?', req.user)
res.send(`Hello, logged in`);
})
When trying this I remove the middleware to see what req looks like, but I always get undefined req.user, so I'm guessing either I'm supposed to be passing req.user somehow in my axios call or I'm not retrieving a user when logging in initially, any ideas on what I'm doing wrong?
Many thanks
You don't have to do anything about the user's data once you log in with authorization, the passport middleware is here for the job, however we should at least set up the right condition for them to exchange the credential, try to change the setting a bit to see how it go :
this part :
app.use(cors({
origin: //put the EXACT URL you host from Vercel
method: 'GET,PUT,POST,DELETE'
credentials: true,
}))
app.set('trust proxy', 1)
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(express.json()); // optional: you can uninstall bodyParser since express have now his own module so you can just replace with this line.
app.use(passport.initialize());
app.use(passport.session());
and update a bit this part at the frontend Axios call :
const config = {
withCredentials: true,
};
I don't know if you have already the issue in localhost or after deployment, if it sill not working than we may have to fix on the cookie session.
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
I am trying to Test if my build file is ok, Therefore what I did was to build client and move build directory to server.
and in the index.js of backend => to open HTML of Build folder.(I do not know if this is right thing to do)
app.get("/", function (req, res) {
res.sendFile(__dirname + "/build/index.html");
});
Following folderStructure
-client
=>src(react Code)
-server
=>Build(which is from react 'npm run build')
=>BackendCode
I have implemented Login Routes one for Post login, and one for get login which is
GET:
router.get("/login", async (req, res) => {
console.log("getLogin ses.user: ", req.session.user);
console.log("getLogin ses.isLogIn?: ", req.session.isLoggedIn);
if (req.session.user) {
return res.send({
isLoggedIn: true,
user: req.session.user,
isAdmin: req.session.isAdmin,
isVerified: req.session.isVerified,
pwFlag: req.session.pwFlag,
});
} else {
return res.send({ isLoggedIn: false });
}
});
POST:
router.post("/login", isNotLoggedIn, async (req, res) => {
-- user find in DB and compare Password in db Logic...--
if(user){
req.session.save((err) => {
if (err) console.log(err);
console.log("Session Saved!!!!: ", req.session);
logger.info(`LOGIN POST/ ${req.session.user.email}`);
return res.status(200).send({
user: sessionUser,
isAdmin: req.session.isAdmin,
pwFlag: req.session.pwFlag,
});
})
})
with this two route, when i do router.post for login it works.
REACT CODE=>
Login Submit form
const onLoginSubmit = async (e) => {
e.preventDefault();
setLoading(true);
const req = { email, password };
const res = await fetch(`${serverUrl}/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", //to let cookie to go to server
body: JSON.stringify(req),
});
const data = await res.json();
if (res.ok) {
console.log("login success");
setLoginStat(true);
setIsAdmin(data.isAdmin);
setUser(data.user);
setPwFlag(data.pwFlag);
if (data.user) {
setLoading(false);
navigate("/");
}
} else {
alert(data.message);
setLoading(false);
}
};
and in the App.js of React,
useEffect(() => {
checkLogin();
}, []);
async function checkLogin() {
const res = await fetch(`${serverUrl}/login`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
credentials: "include",
});
try {
const data = await res.json();
console.log("App useEffect: ", data);
if (res.ok) {
console.log(data);
setLoginStat(data.isLoggedIn);
setUser(data.user);
setIsAdmin(data.isAdmin);
setIsVerif(data.isVerified);
setPwFlag(data.pwFlag);
}
} catch (err) {
setLoginStat(false);
}
}
so the Login Post works well it make me to go to main page. but when I go to different Page that has useEffect to fetch Data, and backend with middle where "isLoggedIn" :
ex)router.get("/something", isLoggedIn, async(req,res)=>{...})
async function isLoggedIn(req, res, next) {
console.log("MW => isLoggedIn:", req.session.user);
console.log("MW => session:", req.session);
if (req.session.isLoggedIn && req.session.user) {
next();
} else {
return res.status(401).send({ message: "Did not login 🚫" });
}
}
it say that MW => isLoggedIn: undefined session being gone. I really dont know why this is happening
To give Detail here is my index.js of server Setting:
const cors = require("cors");
const express = require("express");
const session = require("express-session");
...
app.use(
session({
store: MongoStore.create({
mongoUrl: process.env.MONGO_URL,
collection: "sessions",
ttl: 1 * 24 * 60 * 60, //this is One day
}),
secret: process.env.MONGO_SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
expires: 60 * 60 * 1000, //one hour
secure: false,
httpOnly: false,
// maxAge: 3600000, //one hour
sameSite: "none",
},
name: "scm",
//5000 === 5sec
})
);
app.set("trust proxy", 1);
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, authorization, Set-Cookie"
);
res.header("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
next();
});
...
app.use(
cors({
origin: true,
// [
// "http://localhost:5000",
// "http://192.168.80.64:3000",
// "http://localhost:3000",
// ],
proxy: true,
credentials: true,
methods: ["HEAD", "POST", "PUT", "GET", "PATCH", "DELETE"],
})
);
...
app.get("/", function (req, res) {
res.sendFile(__dirname + "/build/index.html");
});
it works fine with npm run start on reactjs. it just does not work with build files. Thank you in advance
React application will serve through the entry build/index.html. This file has to be picked up by the back end server to serve the front end application: import the path module and add
app.use(express.static(path.resolve(__dirname, "./build")));
app.get("/", (req, res) => {
res.sendFile(path.resolve(__dirname, "./build", "index.html"));
});
See Refrece.
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
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}