For some reason I am unable to store cookie in my browser.
This is the first time I am working with React And NodeJS
My React application is working on localhost:3000 and NodeJS application on localhost:8080
The Git repository for the same happens to be this
So, I am successfully able to login, store the credentials in DB and probably serialise and de-serialise.
I am not sharing the code for Google Strategy and serialise and de-serialise since I believe that problem doesn't presist here (In case you think that you would need to view it click here
This Google redirect returns at following callback
router.get("/google/callback", passport.authenticate('google', { failureRedirect: "/", session: false }), (req, res) => {
res.redirect("http://localhost:3000/")
})
In my server.js (main file, I start node server by doing node server.js), I am doing this to store cookie
app.use(cors({
credentials: true,
origin: ['http://localhost:3000'] // here goes any Frontend IP Adress
}))
//We are setting cookie here
app.use(cookieSession({
maxAge: 24*60*60*1000, //cookies is valid for a day
keys: ['fgfhfjhfad']
}))
app.use(passport.initialize())
app.use(passport.session())
And then when I do this in my react frontend
componentWillMount() {
axios.get("http://localhost:8080/", {withCredentials: true}).then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
}
Where my localhost:3000/ looks like this
app.get("/", (req, res) => {
if (req.user) {
if (req.isFormFilled) {
res.redirect("http://localhost:3000/home")
} else {
res.json(req.user)
}
} else {
res.json("user does not exsist")
}
})
It always log res.json("user does not exsist") but if I directly go to localhost:3000 in my browser, I can see my req.user < [See: update below question]
Ps; I am enabling cross-origin request in my browser
[Question:] Can someone please help me in finding out what I could be doing wrong?
[Update:] It appears we might be having crocs error, I have changed my code and I am getting this as an error in frontend
Access to XMLHttpRequest at 'localhost:8080' from origin
'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'. The
credentials mode of requests initiated by the XMLHttpRequest is
controlled by the withCredentials attribute
If I remove {withCredentials: true} in my axios request the above error disappears but then it logs user does not exsist in response.data
So send Cookies via REST its neccecary to:
Set Cors serverside:
app.use(cors({
'allowedHeaders': ['sessionId', 'Content-Type'],
'exposedHeaders': ['sessionId'],
'credentials': true,
'origin': ['http://[FRONTEND-IP]:[FRONTEND-PORT]', 'http://[ALTERNATIVE-FRONTEND-IP]:[FRONTEND-PORT]'],
}))
For Frontend you need to setup a call like this:
fetch('http://[API-IP]:[API-PORT]/call', {credentials: 'include'}).then((result) => {
return result.json()
}).then((data) => {
//Do something
});
you can also use fetch asynchronous:
async function loadData() {
let response = await fetch(
'http://[API-IP]:[APi-PORT]/call',
{credentials: 'include'}
);
return response.json();
}
this, of course, applies to using a rest service with json bodies.
If you rely on another structure than json, you need to parse your response manually.
Also, An interested article on web about cors https://50linesofco.de/post/2017-03-06-cors-a-guided-tour
If you face Problems with Passport-Sessions try to use 'express-session' instead.
'express-session' Creates Cookies itself and sends it.
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
If you use this you can grab the session in each rest call.
app.get('/', (req, res) => {
req.session.[anyVariableUWantHere] = true;
res.send('done');
});
Now the session is created.
If you get any Problems with React try this:
in server.js:
app.use(cors({
credentials: true,
[...]
}))
now you just need to edit your 'fetch' in React like that:
fetch('http://localhost:3000/call', {credentials: 'include'}).then((result) => {
return result.json()
}).then((data) => {
//Do something
});
The credentials: 'include' is important for React to get the Session-Cookie.
By Default fetch is not loading any Cookies.
Related
I have a problem saving data to the Express Session middleware. I am using a Vue.js frontend to communicate with the server which is running at localhost:8080. The server runs on localhost:3002.
I suspect that the interaction between the Vue app and the server may be the source of the problem as I have tried tests with a bare bones Express app that serves HTML as simple template literals and req.session.save() works fine.
Here is my code.
vue.config.js
module.exports = {
"transpileDependencies": [
"vuetify"
],
devServer: {
"https": true
}
}
index.js(Express server)
const corsOptions = {
origin: 'https://localhost:8080', // Have tried with and without these options
credentials: true,
};
app.use(cors(corsOptions));
// Set up app to use session
let sess = {
secret: 'What secret?',
resave: false, // Tried true
saveUninitialized: false, // Tried true
cookie: {secure: process.env.NODE_ENV=="prod",httpOnly: false}, // Tried true
store: MongoStore.create({ mongoUrl: process.env.DB_URL,
ttl: 14 * 24 * 60 * 60 }) // = 14 days. Default
}
app.use(session(sess));
Login route where data is being set to the session.
app.post('/api/login', async (req, res) => {
...
request(options, function (error, response) {
if (error) throw new Error(error)
let fm_res = JSON.parse(response.body)
req.session.FM_TOKEN = fm_res.response.token
req.session.save()
console.log('TOKEN STORED IN SESSION :: ', req.session) // token present in session here
res.json({message: 'Token saved in session...', status: 200})
});
});
Separate route where token is not accessible.
// CHECK AUTH
app.post('/api/token_auth', async (req, res) => {
let authToken = req.session.FM_TOKEN
console.log('TOKEN FROM SESSION : ', authToken) // undefined
....
});
I have researched and tried various solutions suggested here on SO but nothing I have found from any answers has worked. Can anyone point me in the right direction to solve this one as I am out of ideas. Thanks in advance.
UPDATE
I have tested the above endpoints with with Postman and they work, i.e. the /api/token_auth has access to the token saved on the session. So, it appears the problem might be connected to the fact that my frontend is not using https but http to make these calls.
I have tried using httpOnly: false setting in the session.cookie as suggested in this SO answer, but it still doesn't work. I am out of ideas.
Does anyone know how https requirement can be circumvented for development purposes?
I'm making a web app. My back end uses Node.js, Express.js, and specifically, it uses the module express-session to create a session object for session-based authentication in order to persist a user login. I understand that when I use express-session to create a session, a cookie with the session ID is created on the back end and sent to the browser on the front end. I have verified that this cookie sends seamlessly when I use my browser and visit the page the Express app is hosted on (in my case, localhost:3001).
// My Express app's index.js file
// This code seamlessly sends a session ID cookie to the browser when I visit http://localhost:3001
const express = require('express');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
} ));
app.listen(3001, () => {
console.log('server started on 3001');
})
And I get my cookie just fine. Here's a screenshot in my Chrome developer tools:
However, I want my front end to be a separate app (I'm using React), and I want to use API requests from my front end to access everything on my back end. As the front end and back end are separate apps, they will need to run on different ports. My front end will run on port 3000 and my back end will continue to run on port 3001. And with that in mind, I'll be running localhost:3000 in my browser instead of localhost:3001.
The only problem is, with these changes, the cookie made by express-session no longer gets sent to my browser, even when I do an HTTP POST (via JavaScript fetch()) from my front end to my back end and get a valid response back.
In a nutshell, my question is: how can I have my express-session session ID cookie saved to my browser when I'm using a separate app for the front end?
Here's my front end API request:
fetch('http://localhost:3001/gimmecookie', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ data: "data" })
})
.then(response => response.json() )
.then(response => {
console.log(response)
})
.catch((error) => {
console.error(error);
});
And here's the slightly-modified-from-before index.js file for my Express app:
// My Express app's index.js file that *should* create and
// send a session-id cookie to my React project
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const corsOptions = {
origin: 'http://localhost:3000',
}
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(cors(corsOptions));
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
}));
app.post('/gimmecookie', (req, res) => {
res.json({ sendsome: "data" })
});
app.listen(3001, () => {
console.log('server started on 3001');
});
Right now, I'm using a hack to send the cookie (creating a cookie in JS and sending it manually), but the hack is getting more and more tiresome as my project gets bigger. What do I need to add to this code to have the cookie send like when I was using just one app? Is express-session just not designed for this? (It seems like it would be, I know it is extremely common to have a separate app for both front end and back end.)
Should I expect the cookie to send automatically if the front end and back end have any sort of handshake? Do I need to mess with the cookie.domain or cookie.sameSite attributes in the express-session initialization object? Do I need to mess with CORS or the fetch() Accept header? Is res.json() the wrong method to use? Is it easier to deal with a real IP and not localhost? I've tried a bunch of things, but I no matter what I do, I can't get that blasted express-session generated session ID cookie on my browser.
It turns out that the problem was with the front end, not the back end, and it was a CORS issue. The express-session code was making the cookie just fine, but the front end couldn't accept it because having the back end hosted on port 3001 made it a cross-origin request (you'll recall the front end was on port 3000), even though both the front and back ends were on the same machine, localhost.
All I had to do was use the proxy field in my package.json file in my React project, like so:
...
},
"proxy": "http://localhost:3001"
}
With the proxy change, I also had to change the fetch() in my React code, from this:
fetch('http://localhost:3001/gimmecookie', {
...
to this:
fetch('/gimmecookie', {
...
as my proxy field was tricking my React project into thinking my back end on a different port was actually on the same origin.
Side note: once I realized this was a front end CORS issue, I toyed around with some other solutions (such as using credentials: include in the fetch() init object), but it quickly became apparent that these solutions had significant drawbacks, until I found the proxy solution. Flippn' CORS!
I have a server-side rendered web app running on localhost:3000 and the API on localhost:3010. How do I set the same cookie on both domains after a request to the API?
When I log in, I'm sending a POST request to localhost:3010 and it's setting a cookie like this:
const token = jwt.sign({ id, email }, secret, { expiresIn });
res.cookie('authorization', token, { signed: true, httpOnly: true, maxAge: 10000000 });
My problem is I can't figure out how to set that cookie on the app at localhost:3000. I was just using localStorage before, but it doesn't work for server-side rendering when I have my API and app on different domains.
Here's how my server-side rendering middleware on localhost:3000 looks like, trying to access said cookie:
import Cookies from 'universal-cookie';
export function serverSideRendering(req, res, next) {
const cookies = new Cookies(req.headers.cookie);
const token = cookies.get('authorization');
// ...
}
Try to change property name "authorization" to "JWT-token";
res.cookie('JWT-token', token, { signed: true, httpOnly: true, maxAge: 10000000 });
or you can try native node js method;
res.writeHead(200, {
'Set-Cookie': 'authorization='+token,
});
And my advice, don't use cookie, use headers fields. It's helps you in future to increase your app to ios and android apps.
And you can keep your token inside store of your app, it's more secure.
I'm doing a project with vue + nativescript
the function app.get is not triggerd when I'm calling it from the vue project
this call :
const urlChannels = 'http://localhost:3001/sources';
axios.get(urlChannels)
.then(response => {
store.commit('setTasks', {
channels: response.data,
});
})
}
returns :"data":"","status":null,"statusText":"" as if the server is off,(the call itself is valid it works with other apis)
but simple test with angularjs on the browser returns the valid needed data
this is my nodejs :
app.get('/sources', function (req, res) {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET'
});
res.writeHead(200,{'Content-Type':'application/json'})
let data = getNews.getSources()
res.send(JSON.stringify(data));
//res.json(data); also tried this same resualt
})
res.end() is intended for ending the response without data (see https://expressjs.com/en/api.html).
If you want to return json, the easiest way is to use res.json():
app.get('/sources', function (req, res) {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET'
});
let data = getNews.getSources()
res.json(data);
});
I found the problem, it's a security thing with ios, they don't allow http calls, only https (I run the project on an ios emulator)
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection
I have a system that works perfectly using postman, I can POST to
localhost:1337/api/user/login with username and password set in the body of the request. This then verifies the login and allows me access to further calls. (And repeated calls to localhost:1337/api/user/login recognize I'm already logged it and respond to that).
When I check the server I notice that postman is indeed sending the (htmlonly) cookie with each request I make.
Now I tried to do the same log in through sencha 6.5 modern. In some controller I have the following code:
const form = this.getView();
if (form.isValid()) {
const data = form.getValues();
const u = 'http://localhost:1337/api/user/login'; //form.getUrl();
debugger;
Ext.Ajax.request({
url: u,
method: 'POST',
params : data,
success: function(response, opts) {
const obj = Ext.decode(response.responseText);
console.dir(obj);
},
failure: function(response, opts) {
console.log('server-side failure with status code ' + response.status);
}
});
}
This makes the "correct" call to the database, and I am seeing the credentials as expected. However I notice that on any future calls the session cookie is not being send stored (chrome doesn't store a cookie), and each time a new session cookie is being send/created.
What creates this discrepancy? Why does sencha/chrome not store the cookie?
For completeness sake, the server in sails (which uses expressjs session storage) goes to:
login: async function(req, res) {
try {
const user = await User.findOne({username: req.body.username});
const sess = req.session;
if (sess.userId === user.id) {
res.json({succes: false, message: 'already logged in'});
return;
}
if (!user) {
res.json({success: false, message: 'Authentication failed. User not found.'});
} else {
// check if password matches
const valid = await comparePromise(req.body.password, user.password);
if(valid) {
// if user is found and password is right
req.session.userId = user.id;
res.json({success: true, user: user});
} else {
res.forbidden(JSON.stringify({ success: false, message: 'Authentication failed. Wrong password.' }));
}
}
} catch (err) {
console.log(err);
res.badRequest(JSON.stringify({success: false, message: err.message}));
}
},
If your webpage is at one port and your ajax call is made to another port, this could explain why cookies are not being saved (even if the rest of the request and response works).
I think that different ports are treated as different origins. This would certainly explain why postman is successful while the code run from the page is not. I can tell you how I solved this problem (not for ports, but for different domain names served by the same ip) in my app...
In the ajax call, you need to set it up to allow credentials since the url is different origin:
$.ajax({
url: 'http://somehost.com/some/path',
method: 'GET',
xhrFields: { withCredentials: true },
success: function(data) {
// the cookie should be set here
}
});
Then you have to allow this at the receiving url. This can be insecure. In my case, I allowed cross origin requests at a very specific url only by configuring the routes.js file:
'GET /some/path': {
controller: 'MyController',
action: 'someAction',
cors: {
origin: '*',
credentials: true
}
},
In my case, that allowed any origin to access that route with credentials, and get a cookie. In your case, you might be able to use something like
cors: {
allowOrigins: ['http://localhost:1337', 'http://localhost:1841']
credentials: true
}
... but I'm not sure I have the syntax just right for that. Some docs are here.
If you need to do more universal setup over your whole app, I think sails has some ways to do that from the cors.js config file, but you should think through the security before doing this.
I think that once you have the cookie, it should be included with all requests across all ports (ie, I don't think including cookies is port specific, only storing them).
Good luck!