How to use cross-origin cookies for server-side rendering? - javascript

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.

Related

Value appears saved to Express Session but not accessible inside subsequent route

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?

Send an express-session generated session ID cookie to a different-origin React front end

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!

Cannot create httponly cookie containing jwt in ASP.NET Core and React

I'm trying to implement an authentication scheme in my app. The controller, more specifically the method, responsible for checking user's credentials and generating jwt which it has to put into the httponly cookie afterward looks as follows
[HttpPost]
[Route("authenticate")]
public async Task<IActionResult> Authenticate([FromBody] User user)
{
var response = await _repository.User.Authenticate(user.Login, user.Password);
if (!response) return Forbid();
var claims = new List<Claim>
{
new Claim("value1", user.Login)
};
string token = _jwtService.GenerateJwt(claims);
HttpContext.Response.Cookies.Append(
"SESSION_TOKEN",
"Bearer " + token,
new CookieOptions
{
Expires = DateTime.Now.AddDays(7),
HttpOnly = true,
Secure = false
});
return Ok();
}
I tested this method in Postman - everything works gently and correctly in there. The cookie is being created as well. Moreover, recently I created an app using Angular where I was using the same authentication method, but with Angular's HTTP module the cookie was being created all the time. Here is what that method looks like in my React app with the usage of Axios
export const authenticate = async (login, password) => {
return await axiosLocal.post('/api/auth/authenticate',
{login, password}).then(response => {
return response.status === 200;
}, () => {
return false;
});
Everything I'm getting in response trying to log in is response code 200. I'm pretty sure it's something about Axios's settings.
Also if someone's curios the variable "axiosLocal" contains the baseURL to the API.
- Update 1
Ok. If I'm not mistaken in order to set a cookie from the response I have to send all the requests with { withCredentials: true } option. But when I'm trying to do that the request is being blocked by CORS, although I had already set a cors policy which has to allow processing requests from any origin like that
app.UseCors(builder => builder.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.AllowCredentials());
I just had the same issue. I fixed it.
Problem:
In browsers, the httpOnly cookie was received and not returned to the server
In Postman working
// Problemable server code for settings httpOnly cookie
Response.Cookies.Append("refreshToken", refreshToken.Token, new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7),
});
Solution:
On the server .AllowCredentials() and
.SetOriginAllowed(host => true) or
.WithOrigins("https://localhost:3000")
On the client (react, axios) withCredentials:true in the headers
If still not working open the Network tab in DevTools in Chrome(current v.91.0.4472.124), select the failed request and when you put the mouse over the yellow triangle you can see very detailed information why the cookie is blocked.
// End server code for setting httpOnly cookie after following the DevTools warnings
Response.Cookies.Append("refreshToken", refreshToken.Token, new CookieOptions
{
HttpOnly = true,
Expires = DateTime.UtcNow.AddDays(7),
IsEssential=true,
SameSite=SameSiteMode.None,
Secure=true,
});
Finally solved. Passing .SetIsOriginAllowed(host => true) instead of .AllowAnyOrigin() to CORS settings with { withCredentials: true } as an option in Axios request helped me.

React Node Unable to pass cookie to the browser (crocs error)

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.

Express session variables don't persist across requests

I'm trying to get sessions variables working in my Express Node.js project, using the express-session module. I'm not getting any errors, and setting session variables seems to work, however they don't persist across requests. Here's a simplified bit of my code:
server.js
var express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var app = express();
app.use(bodyParser.json());
// Sessions
app.use(session({
secret: config.secret
}));
app.use('/api/projects', require('./controllers/api/projects'));
var server = app.listen(3000, function() {
console.log('Server listening on', 3000)
});
api/projects.js router
var router = require('express').Router()
router.get('/set', function(req, res, next) {
req.session.test = "test";
res.status(200).json({"test":req.session.test});
});
router.get('/current', function(req, res, next) {
res.status(200).json({"test":req.session.test});
})
Setting the variable seems to works...
The /set API call should set a session variable called test. This API call appears to work when I try it with curl:
curl -X GET localhost:3000/api/projects/set --cookie "connect.sid=s%3AyP3a8siRuA-5jDxWH4T03UxNpFd6lfBq.Ha8b8eJxbtW8fAJlbgR9jumfmBpJIXNE6444fOb2Jro"
{"test":"test"}
This is also confirmed in the console log:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
test: 'test' }
...however next time I check it it's not set
When I try to check the variable with my second API call, it appears to be undeclared:
curl -X GET localhost:3000/api/projects/current --cookie "connect.sid=s%3AyP3a8siRuA-5jDxWH4T03UxNpFd6lfBq.Ha8b8eJxbtW8fAJlbgR9jumfmBpJIXNE6444fOb2Jro"
{}
This is confirmed in the console log, the test variable is no longer set:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
How can I ensure my sessions variables persist across requests?
(PS: I've been stuck on this for a while and any small comment on hints or resolution is very welcome, even if you're not sure you've got the answer)
I can see two possible reasons for this.
First, you could've messed up with cookies in curl.
You could ensure that you've set your cookies correctly by checking http response. If it contains Set-Cookie header with a new connect.sid then you did something wrong.
Alternatively, you could use a web browser with native cookies support to guard yourself against such mistakes.
Second, you may've restarted your sever between two requests.
Since you didn't specify any persistent store for your sessions, no data will persist between node.js server restarts.
If you want session data to persist after node.js server stops or restarts, you should consider using some persistent session store (i.e. redis-store).

Categories