Express Session property differentiates between browser and Postman - javascript

UPDATE
I think it's worth mentioning I am running Angular CLI which runs on port 4200 and my server is running on port 8080. Could this be a problem? It's the only thing I can think of at the moment
When I make a call to my route '/auth/login' I set a loggedIn property on the session object. To check a user is authenticated, a request is made to '/auth/checktoken'. In here, I check for the presence of the loggedIn property on the req.session object. When I do these requests within Postman everything works perfectly fine, but when using the browser my session.loggedIn property is undefined. I will paste the relevant code below. Thanks in advance for any help
Server Side
router.get('/checktoken', (req, res) => {
if(!req.session.loggedIn) {
return res.status(401).send({
userTitle: 'Not authorised',
userMessage: 'You are not authorised to view this'
})
}
return res.status(200).send()
})
Client Side
#Injectable()
export class CheckAuthenticationService implements CanActivate {
constructor(
private router: Router,
private http: HttpClient) { }
canActivate() {
this.http.get('http://localhost:8080/auth/checktoken', { responseType: 'text' })
.toPromise()
.then(() => {
this.router.navigate(['admin']);
})
.catch( () => {
this.router.navigate(['login']);
});
return true;
}
}
Snippet of login code that sets the loggedIn property
if (user) {
user.comparePassword(password, (err, isMatch) => {
if (isMatch && isMatch) {
req.session.loggedIn = user;
res.status(200).send()
} else {
res.status(404).send({
userTitle: 'Wrong password',
userMessage: 'Please make sure your password is correct'
});
}
});
}
Session Store setup
app.use(session({
name: 'jack-thomson',
secret: SECRET_KEY,
saveUninitialized: false,
resave: true,
store: new MongoStore({
mongooseConnection: mongoose.connection
})
}))
This all works in Postman but when hitting these endpoints on the client, .loggedIn is undefined, always

I had the same problem before. I think it's about cors credential. I use Axios on React to POST data login to my Express backend application. I need to add these lines:
import axios from 'axios';
axios.defaults.withCredentials = true;
Then on my Express project, I add cors:
var cors = require('cors');
app.use(cors({
credentials: true,
origin: 'http://localhost:3000' // it's my React host
})
);
Finally I can call my login function as usual, for instance:
signup(){
var url = 'http://localhost:3210/'
axios.post(url, {
email: this.refs.email.value,
username: this.refs.username.value,
password: this.refs.password.value,
passwordConf: this.refs.passwordConf.value
})
.then((x)=>{
console.log(x);
if(x.data.username){
this.setState({statusSignup: `Welcome ${x.data.username}`});
} else {
this.setState({statusSignup: x.data});
}
})
.catch((error)=>{console.log(error)})
}
login(){
var url = 'http://localhost:3210/';
var data = {
logemail: this.refs.logemail.value,
logpassword: this.refs.logpassword.value,
};
axios.post(url, data)
.then((x)=>{
console.log(x);
if(x.data.username){
this.setState({statusLogin: `Welcome ${x.data.username}`});
} else {
this.setState({statusLogin: x.data});
}
})
.catch((error)=>{console.log(error)})
}
And it works! Hope this solve your problem.

Are you using CORS?
I had the same problem, and i solved it by putting { withCredentials: true } as optional arguments in every request.
I mean whenever you send a http/https request in your service, put this as last argument, and you are good to go.
You can read this and this Stackoverflow question for more information on the topic.

I have finally figured out what is going on. My Angular CLI was running on 4200 and my server was running on a separate port. I have gotten over the issue with serving my application with express so it is all one one route. This has solved the issue for me. If anyone comes by this I hope this information comes in handy to you!

Related

request to login to a node server, using react

Here is the situation:
I have a database which contains a user and password registered.
My assignment, for now, is to create a login form, and login with a registered uname and pw.
Uname and pw are registered in the server/database already.
ps: I did not create the server nor database.
Node server code
import express from 'express';
import cors from 'cors';
import http from 'http';
import { Sequelize } from 'sequelize';
import { Data } from './database';
import { router } from './routes/Router';
import { initialData } from './database/someData';
const closeServer = async (
server: http.Server,
sequelize: Sequelize,
signal?: string
) => {
server.close();
await sequelize.close();
process.exit();
};
const runServer = async (): Promise<void> => {
const PORT = process.env.PORT || 8082;
const app = express();
const sequelize = Data.init();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use('/api', router);
const server = app.listen(PORT, () => {
console.log(`Starting server at ${PORT}`);
});
try {
await sequelize.authenticate();
await sequelize.sync({
force: process.env.SERVER === 'reset',
});
if (process.env.SERVER === 'reset') await initialData();
} catch (e) {
closeServer(server, sequelize);
throw e;
}
};
runServer()
.then(() => {
console.log('Run successfully');
})
.catch((ex: Error) => {
console.log('Unable to run:', ex);
});
I need help on what is that I have to do.
When I input username and pw, on the form, what are the methods to use for sending the info?
And then, when the info reaches the server, i think the username and pw need to be validated with jwt, and then check if the user and pw exists. how do i do that?
What i have understood so far is that i gotta use axios to send info to server, but thats it.
Do i need to use jwt for the login?
What is the normal flow for this kind of mechanism?
I am using react as a framework.
So there are quite few steps here.
First you have to create endpoint on your backend server for issuing jwt tokens. Jwt tokens can be used as a pass for user to login. So in your router you would add something like this:
router.post('/login', (req, res)=> {
const username = req.body.username
const password = req.body.password
// Then you make db call to verify username and password are correct.
if Credentials are valid, you would issue jwt token
jwt.sign({
// here you can save extra information of user. Also remember this information must be public since anyone can see it. Do not put user password here
email: 'email',
userId: 'id',
}, "secret")
})
After this, you need some kind of middleware on backend, so that on each user request, you check and verify this jwt token which is sent from react application. For example you could write isAuth middleware:
const jwt =require("jsonwebtoken");
export const isAuth= (req, res, next) => {
try {
// here we attach request in auth header, with Bearer "jwt token" format. So we extract jwt token and verify it
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: "no token" });
}
const token = authHeader.split(" ")[1];
let decodedToken;
decodedToken = jwt.verify(token, "secret");
if (!decodedToken) {
return res.status(401).json({ message: "Wrong token" });
}
req.userId = decodedToken.userId;
next();
} catch (err) {
console.error(err);
return res.status(401).json({ message: err });
}
};
Now you would be able to have backend endpoints like this:
// This is how you would require login on some routes
router.post("/getMyPrivateInfo", isAuth, QueryPrivatInfo)
Now on React side, you would make request for login like this:
axios.post("/login", {
username: '1',
password: "2"
})
This would return jwt token, now you would save this token in local storage.
After its saved in local storage and you make request with axios for private info you would do following
axios.post("/getMyPrivateInfo", {any request body info neeeded}, {
headers: {
Authorization: "Bearer jwtTokenFromLocalStorage"
}
})
This is how whole flow will work, hope it makes sense

Express.js backend not saving cookie to Nuxt.js frontend

I've built an authorization server with Express.js that works when testing with Postman where it saves the access and rotating refresh token as signed cookies.
However, what I completely forgot, was that my backend and frontend are two completely separate servers and domains. I tried enabling the Access-Control-Allow-Credentials, but that didn't work. I don't get any error messages, the cookies simply aren't being saved.
Here is my login controller from the backend:
const userLogin = async (req, res, next) => {
const { email, password } = req.body;
if (!email || !password) return res.sendStatus(400);
try {
const login = await loginUser(email, password);
if (login == 'wrong_email_or_password') return res.status(200).json({ error: login });
res.header('Access-Control-Allow-Credentials', true);
res.cookie('access', login.accessToken, { httpOnly: true, secure: (env != 'dev'), signed: true });
res.cookie('refresh', login.refreshToken, { httpOnly: true, secure: (env != 'dev'), signed: true });
res.status(200).json(login);
} catch(e) {
console.log(e.message);
res.sendStatus(500) && next();
}
}
And after I encountered some CORS issues, I also did app.use(cors({ origin: true, credentials: true }));, which resolved the issue. For production I'll probably also add a CORS-whitelist.
And here's my Nuxt.js method that will be called upon submitting the register-form:
async register () {
try {
if (!(this.firstname && this.lastname && this.username && this.email && this.password)) throw new Error('Pflichtfelder ausfüllen')
const res = await axios.post(`${this.$axios.defaults.baseURL}register`, {
firstname: this.firstname,
lastname: this.lastname,
username: this.username,
email: this.email,
password: this.password
}, {
withCredentials: true,
credentials: 'include'
})
console.log(res)
this.$router.go()
} catch (e) {
this.error = e.message
console.error(e)
}
}
I wish I could provide some more helpful information other than "doesn't work, help pls", but as I said, I don't get any errors whatsoever, so I really don't even know where to look. :(
Alright, it would seem that what I'm trying to do is impossible. Cookies coming from third-party domains are not allowed, which makes sense now that I think of it...
For anyone interested, my solution is to just save both tokens in localStorage. Since I am using rotating refresh tokens this isn't that big of a deal.

What am I doing wrong with sessions?

so Ive finally deployed my app and resolved all the CORS issues, but I have a problem with user authentification. I can log-in, but when I refresh a site I get automaticly logged out -> on all browsers beside Mozilla Firefox, there it works somehow.
userContext.js -> Front-end
//XXX Login
const login = () => {
Axios.post(`${apiUrl}/users/login`, {
userName: nameLog,
userPassword: passwordLog,
}).then((response) => {
console.log(response);
if (!response.data.errors) {
setUser(response.data.name);
setUserId(response.data.user_id);
} else {
console.log(response);
const errors = response.data.errors;
console.log(errors);
processErrors(errors);
}
});
};
//Checking if user is logged in on every refresh of a page
useEffect(() => {
Axios.get(`${apiUrl}/users/login`).then((response, err) => {
console.log("GET /login RESPONSE: ", response);
if (response.data.loggedIn === true) {
setUser(response.data.user[0].name);
setUserId(response.data.user[0].user_id);
}
});
}, []);
First call is a POST request, thats when user logs in using form on my site.
And second one is a GET request, that checks if the session returns loggedIn true, this is called on every refresh of a page as it is inside useEffect hook.
Then I update my userState which acts as auth if user is allowed to do some action or not.
userRoutes.js -> Back-end
//Login user
router.post(
"/login",
[
check("userName").exists().notEmpty().withMessage("Username is empty.").isAlpha().isLength({ min: 3, max: 40 }),
check("userPassword").exists().notEmpty().withMessage("Password is empty.").isLength({ min: 3, max: 60 }).escape(),
],
(req, res) => {
const valErr = validationResult(req);
if (!valErr.isEmpty()) {
console.log(valErr);
return res.send(valErr);
}
const name = req.body.userName;
const password = req.body.userPassword;
const sql = "SELECT * FROM users WHERE name = ?";
db.query(sql, name, (err, result) => {
if (err) throw err;
if (!result.length > 0) {
res.send({ errors: [{ msg: "User doesn't exist" }] });
} else {
//compare hashed password from front end with hashed password from DB
bcrypt.compare(password, result[0].password, (error, match) => {
if (error) throw error;
//if passwords match send -> create session and send user data
if (match) {
req.session.user = result;
res.send({ user_id: result[0].user_id, name: result[0].name });
} else {
res.send({ errors: [{ msg: "Wrong username or password" }] });
}
});
}
});
}
);
//Checking if user is logged in and if so, sending user's data to front-end in session
router.get("/login", (req, res) => {
console.log("GET /login SESSION: ", req.session);
if (req.session.user) {
res.send({ loggedIn: true, user: req.session.user });
} else {
res.send({ loggedIn: false });
}
});
Again first one is for the POST request, where I create session and send it in response filled with user's data (name,id) to front-end (then I update my states accordingly).
Second one belongs to the GET request and returns false if user is not logged in or true + user's data. Then once again I update my states.
However this doesnt work and I dont know why. As I mentioned it returns loggedIn: false on every browser besides Mozzilla Firefox.
This is my first time dealing with sessions and cookies so what am I missing here?
By the way the site url is here if that helps: LINK, I left some console.logs() to display responses.
EDIT: adding all middleware
app.js -> main nodejs file
const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const cors = require("cors");
const { check, validationResult } = require("express-validator");
const userRoutes = require("./routes/userRoutes.js");
const app = express();
app.use(express.json());
app.use(
cors({
origin: [
"http://localhost:3000",
"https://todo-react-node.netlify.app",
],
methods: ["GET, POST, PATCH, DELETE"],
credentials: true, //allowing cookies
})
);
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use((req, res, next) => {
console.log("SESSION 2 : ", req.session);
console.log("Cookies 2 : ", req.cookies);
next();
});
app.use(
session({
key: "userID",
secret: "subscribe",
resave: false,
saveUninitialized: false,
})
);
app.use("/users", userRoutes);
Your useEffect only called once if you are using [] in useEffect function.
Try to take it out(or add the suitable dependencies) and add a console.log() in that function to test out.
Furthermore, read the following article to gain some insight about useEffect.
Okay so finally Ive managed to solve this issue with sessions and cookies. Solution at the end.
What was causing problems?
Cross domain cookies (probably). As Ive mentioned everything worked fine when Ive hosted my app on localhost, but as soon as Ive deployed my app to 2 different hostings (Netlify - front end, Heroku - back end) my cookies werent coming trough.
Ive actually managed to send cookie by
res.cookie("cookie_name", "cookie_value", {sameSite: "none" secure: true})
, however It was standalone cookie and not associated with a session.
Sure why dont you edit your settings in express-session then? Ive tried -> didnt work. My session was never created. I could only send standalone cookie.
And another problem I couldnt delete this cookie in browser after being set, nor I could modify it. Ive tried res.clearCookie("cookie_name") or setting maxAge attribute to -1, nothing worked.
After googling and watching a lot of videos I found out that sending and receiving cookies from different domains is restricted because of security.
SOLUTION -> How did I fix the problem?
Well Ive came upon a VIDEO on YouTube, that showed how to host full-stack application on Heroku. That way your front end and back end are on the same domain and voilà sessions and cookies are working properly. Basically it goes like this:
1) Build your React app
2) In /server (main back end folder) create /public folder and move content of your /client/build directory here
3) In your back end main file (app.js in my case) add app.use(express.static("public")); to serve your front end.
4) Change cors settings to:
app.use(
cors({
origin: "your Heroku app url",
credentials: true, //allowing cookies
}));
5) Change express-session settings
app.use(
session({
name: "name of session",
key: "your key",
secret: "your secret",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24,
},
}));
6) Fix your routes for API calls (currently what Im working on)
7) Host it
Create session:
req.session.SESSION_NAME = session_data; Note that SESSION_NAME has to be identical with "name" attribute's value you have declared in step 5
Delete session:
res.clearCookie("SESSION_NAME"); req.session.destroy();
Hope this helps somebody, who encountered this issue.

Facing CORS error even after adding CORS options on React/Node/Passport for Google Authentication

I am building a simple app with React as frontend and Node/Express/MongoDB as backend. I am authenticating user using Passport. Local authentication is working, as well as Google authentication.
I just seem to not able to load the google login page through the app. I am getting CORS error. I have shared the error below.
On React Login page:
const onClick = async () => {
await Axios.get('/auth/google');
};
Proxy Middleware:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(createProxyMiddleware('/auth', { target: 'http://localhost:4000' }));
};
Node Server.js:
app.use('/auth', require('./routes/auth'));
routes/auth file:
const cors = require('cors');
var corsOptions = {
origin: 'http://localhost:3000',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
};
router.get(
'/google',
cors(corsOptions),
passport.authenticate('google', {
scope: ['profile', 'email'],
}),
);
router.get('/google/redirect',cors(corsOptions), passport.authenticate('google'), (req, res) => {
res.send(req.user);
});
passportconfig.js:
passport.use(
new GoogleStrategy(
{
clientID: ClientID,
clientSecret: ClientSecret,
callbackURL: '/auth/google/redirect',
proxy: true,
},
(accessToken, refreshToken, profile, done) => {
// passport callback function
//check if user already exists in our db with the given profile ID
User.findOne({ googleId: profile.id }).then((currentUser) => {
if (currentUser) {
//if we already have a record with the given profile ID
done(null, currentUser);
} else {
//if not, create a new user
new User({
googleId: profile.id,
})
.save()
.then((newUser) => {
done(null, newUser);
});
}
});
},
),
);
Error:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fgoogle%2Fredirect&scope=profile%20email&client_id=<clientID>.apps.googleusercontent.com' (redirected from 'http://localhost:3000/auth/google') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If I click on the above XMLHttpRequest link, I am able to authenticate and an account is created on my DB with googleID.
I have tried different options suggested throughout internet, but none of them is working for me. I am not sure what is going wrong here.
According to the documentation, try removing the corsOptions entirely and just use the cors() function in your express middle-ware before any router is declared. Like so:
app.use(cors());
Let me know if this works.
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.

Can't delete cookie in express

Pretty simple. I set a cookie like so in my /user/login route:
if (rememberMe) {
console.log('Login will remembered.');
res.cookie('user', userObj, { signed: true, httpOnly: true, path: '/' });
}
else {
console.log('Login will NOT be remembered.');
}
I've already set my secret for cookie-parser:
app.use(cookieParser('shhh!'));
Pretty basic stuff. Everything is working great insofar as I'm able to retrieve whatever I stored in the cookie:
app.use(function (req, res, next) {
if (req.signedCookies.user) {
console.log('Cookie exists!');
req.session.user = req.signedCookies.user;
}
else {
console.log('No cookie found.');
}
next();
});
This middleware is called before anything else, so for the sake of the argument "Cookie exists!" is always logged in my console if the cookie is valid.
The problem is when I try to delete the cookie. I've tried res.clearCookie('user'), res.cookie('user', '', { expires: new Date() }), and I've tried passing in the same flags (that I pass to res.cookie() in /user/login). I've attempted to use combinations of these methods, but nothing has worked.
Currently, the only way I am able to clear the cookie (and not receive the "Cookie exists!" log message) is by clearing my browser history. Here is what my logout route looks like:
route.get('/user/logout', function (req, res, next) {
res.clearCookie('user');
req.session.destroy();
util.response.ok(res, 'Successfully logged out.');
});
It seems as though I can't even modify the cookie value; I put
res.cookie('user', {}, { signed: true, httpOnly: true, path: '/' })
in my logout route, but the cookie value remains unchanged.
I realized after a long and annoying time that my front end was not sending the cookie to the end point were I was trying to clear the cookie...
On the server:
function logout(req, res) {
res.clearCookie('mlcl');
return res.sendStatus(200);
}
And on the front end,
fetch('/logout', { method: 'POST', credentials: 'same-origin' })
adding the "credentials: 'same-origin'" is what made the clearCookie work for me. If the cookie is not being sent, it has nothing to clear.
I hope this helps. I wish I had found this earlier...
Even though it's not gonna help the author of this question, i hope this might help someone.
I run into the same problem that i could not delete cookies in my React app that was using Express api. I used axios, and after a couple of hours i was finally able to fix it.
await axios.post('http://localhost:4000/api/logout', { } , { withCredentials: true })
{ withCredentials: true } is what made it work for me.
This is my Express code:
const logOutUser = (req, res) => {
res.clearCookie('username')
res.clearCookie('logedIn')
res.status(200).json('User Logged out')
}
Judging by (an extensive) search and a random thought that popped into my head, the answer is to use
res.clearCookie('<token_name>',{path:'/',domain:'<your domain name which is set in the cookie>'});
i.e.
res.clearCookie('_random_cookie_name',{path:'/',domain:'.awesomedomain.co'});
Note the . which is specified in the cookie, because we use it for subdomains (you can use it for subdomains without the dot too, but it's simply safer to use one).
TLDR; You have to provide a route and domain: in the backend, so that the request is made to the same endpoint in the frontend.
Make sure you are sending your credentials to be cleared
Even though it's only a /logout endpoint, you still need to send credentials.
// FRONT END
let logOut = () => {
fetch('logout', {
method: 'get',
credentials: 'include', // <--- YOU NEED THIS LINE
redirect: "follow"
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
}
// BACK END
app.get('/logout', (req, res) => {
res.clearCookie('token');
return res.status(200).redirect('/login');
});
Had a nightmare getting this to work as well this works for me hopefully helps someone.
Express router
router.post('/logout', (req, res) => {
res.clearCookie('github-token', {
domain: 'www.example.com',
path: '/'
});
return res.status(200).json({
status: 'success',
message: 'Logged out...'
});
});
React frontend handle logout.
const handleLogout = async () => {
const logout = await fetch('/logout', {
method: 'POST',
credentials: 'include',
});
if (logout.status === 200) {
localStorage.clear();
alert('Logged out');
} else {
alert('Error logging out');
}
};
I am setting the cookie in my auth call like this.
res.cookie('github-token', token, {
httpOnly: true,
domain: 'www.example.com',
secure: true
});
Important you need to add the path and domain in the clearCookie method.
Path needs to be correct. In my case it was a typo in path
Nov 2022 (Chrome) - What worked for me
Frontend:
const logOut = async () =>{
await axios.post(LOGOUT_URL, {}, {withCredentials: true}) // <-- POST METHOD, WITH CREDENTIALS IN BODY
}
Backend:
res.clearCookie('jwt') // <- NO EXTRA OPTIONS NEEDED, EVEN THOUGH HTTPONLY WAS SET
return res.sendStatus(204)
I am new to this but adding a return (return res.sendStatus(204);)to Backend function is what deleted the cookie for me, hope it helps.
without return it does not delete the cookie but only logs "session over"
app.post("/logout", (req, res) => {
if (req.session.user && req.cookies.user_sid) {
res.clearCookie("user_sid");
console.log("session over");
return res.sendStatus(204);
} else {
console.log("error");
}
});

Categories

Resources