How to send firebase session cookie from server to frontend - node.js

The problem that I am facing is that the session cookies created on the server seem to not be available on the browser. I'm using firebase session cookies which can be found here: ( https://firebase.google.com/docs/auth/admin/manage-cookies )
Below is the code I have
Server
index.js
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.use(cors());
app.use(express.urlencoded({extended: true}));
app.use(express.json());
user.js
userRouter.post('/sessionLogin', (req, res) => {
console.log("Got session login request");
// Get the ID token passed and the CSRF token.
const idToken = req.body.idToken.toString();
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
fb.auth().createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.setHeader('Cache-Control', 'private');
res.cookie('__session', sessionCookie, options);
return res.send(JSON.stringify({status: 'success'}));
}).catch((error) => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
Frontend
fb.auth.signInWithEmailAndPassword(email, password).then(user => {
return user.user.getIdToken().then(idToken => {
console.log(idToken);
//document.cookie = '__session=' + idToken + ';max-age=3600';
return ref.postIdTokenToSessionLogin(idToken);
});
})
When I use postman I'm able to see the session created as expected
postman session picture
My server and frontend are hosted on different domains. I can't seem to wrap my head around this any ideas would be highly appreciated.
Thanks,

You cannot share cookies across domains (unless they are subdomains). See Cross-Domain Cookies for a related discussion.

Related

Nodejs server doesn't recognize saved cookie sessionId

I have a nodejs/express server with the following code
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cookieparser = require("cookie-parser");
const { randomBytes } = require('crypto');
const COOKIE_SECRET = 'aavslrhe158ewuycvasjy7et2hvh2ytt0';
var SESSIONS = {};
app.use(
express.static(__dirname + '/public'),
bodyParser.urlencoded({ extended: false }),
bodyParser.json(),
cookieparser(COOKIE_SECRET)
);
app.get("/login", function (request, response){
response.sendFile(__dirname + "/views/login.html");
});
app.post("/verifyaccount", function (request, response){
const nextSessionId = randomBytes(16).toString('base64');
response.cookie("sessionId", nextSessionId, { maxAge: 3600, httpOnly: true, Secure: true });
SESSIONS[nextSessionId] = request.body.sz_Username;
response.status(response_status).redirect('/admin');
}
app.get("/admin", function (request, response){
if(!is_authorized(request.cookies.sessionId)){
response.redirect('/login');
}
else{
response.sendFile(__dirname + "/views/admin.html");
}
});
app.post("/addproject", function(request, response){
if(!is_authorized(request.cookies.sessionId)){
response.redirect('/login');
}
else{
}
}
function is_authorized(sessionId){
var authorized = false;
if (SESSIONS[sessionId]) {
authorized = true;
}
return authorized;
}
So when I login the credentials go to /verifyaccount, there I check if they're correct. Then it creates a cookie in my browser: sessionId:"KlS6xuspQ4GczVqqpSc2Nw%3D%3D" and stores it in the SESSIONS variable. I get redirect to /admin where the authorization works.
But when I am in admin.html and send data to /addproject I get redirect to /login because the authorization fails. The request.cookies.sessionId is undefined. The cookie keeps existing in my browser, so I don't know what the problem is, since the cookie was correct in /admin.
Edit: after being redirect to /admin from /login if I go back to /login or / and then attempt to /admin from the url I get the same undefined error. Which should not occur since the cookie expires in 1 hour.
"maxAge is in milliseconds. Your cookie is expiring 3.6 seconds after you set it"
-clubby789 htb

Setting single cookie with multiple values

I am trying to set a single cookie key-value pair with multiple values in nodeJS. The reason for the single cookie is that I'm sending Token and Secret variables that are linked as part of authentication, separating these out over two cookies may cause issues. I followed a tutorial that suggests stingify method.
When tested in Postman, the cookie seems to be encoded with token and secret. i.e. '%7B%22' etc. Is this as expected, if yes how do I parse values when the cookie is sent to server.
// userToken and mySecret test values.
CustomerRoute.post('/login', (req, res) => {
...
...
var mycookie = JSON.stringify({userToken:1234,mySecret:5678});
res.cookie('ID', mycookie, {HttpOnly:true, maxAge:20*60*1000, sameSite: 'strict'});
res.apiSuccess(resInfo);
Cookie in postman:
ID=%7B%22userToken%22%3A1234%2C%22mySecret%22%3A5678%7D; Path=/; Domain=localhost; Expires=Tue, 02 Mar 2021 17:37:24 GMT;
UPDATE -
I also managed to send the two tokens without stingify by simply concatenating the two strings.
var mycookie = 'Token='+'1234'+'Secret='+'5678';
UPDATE2
I'm using cookie-parser. When I call route:
.post('/data1', (req, res) => {
//var rc = req.headers.cookie;
const { cookies } = req;
console.log(cookies);
res.apiSuccess();
In console I get:
{ ID: '{"userToken":1234,"mySecret":5678}' }
Whats the best method to split values to variables?
UPDATE3 - As a recap. I want to write a single cookie with userToken and mySecret, then in the /data route verify (this will eventually form a function in middleware)
const http = require('http');
const https = require('https');
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
var cookieParser = require('cookie-parser')
const config = require('./config');
var corsOptions = {
origin: 'http://example.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
/* init server */
const server = express();
/* middleware */
server.use(express.json());
server.use(cookieParser());
server.use(express.static('public'))
server.use(bodyParser.json({
limit: "10000kb"
}));
server.use(bodyParser.urlencoded({
extended: true,
limit: "10000kb"
}));
server.use(function (req, res, next) {
res.apiError = function (message) {
res.json({
status: false,
message: message
})
};
res.apiSuccess = function (data) {
res.json({
status: true,
data: data
})
};
next();
})
Create cookie - login function
var mycookie = JSON.stringify({userToken:1234,mySecret:5678});
res.cookie('session_id', mycookie, {HttpOnly:true, maxAge:20*60*1000, sameSite: 'strict'});
Read cookie:
CustomerRoute.post('/data1', (req, res) => {
// var rc = req.headers.cookie;
const { cookies } = req;
console.log(cookies);
if ('session_id' in cookies) {
console.log('Session Id exists');
var points = JSON.parse(cookies);
//console.log(cookies['id']);
console.log(points);
}
res.apiSuccess();

Express cookie-session not creating cookie on client side

I have a simple ExpressJS app that is using cookie-session to create a cookie. I have a few routes defined but none are returning a cookie. The documentation for cookie-session says that req.session needs to be altered to set the cookie, which is what I'm doing, but it's not working. I'm not seeing any cookie in when I inspect the Application cookies in Chrome. My app looks like this:
const express = require('express');
const cookieSession = require('cookie-session');
const { v4: uuid } = require('uuid')
const app = express();
app.use(express.json())
app.use(cookieSession({
name: 'shortlinks',
keys: [process.env.SESHSECRET],
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
}))
app.use(function(req, res, next) {
console.log(`${req.method} ${req.url}`);
req.session.id = (req.session.id || uuid());
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
next();
})
app.get(
'/api/links',
(req, res, next)=> {
res.json(readDb());
next();
}
)
What do I have to do to have the cookie created?
i hade the same probleme , and i used 'cookies' library i worked well for me...
https://www.npmjs.com/package/cookies
1.npm uninstall cookie-session
//for deleting the old one
2.npm install cookies
//install cookies
and then use it like that
const Cookie = require('cookies')
//inside your route set the token
const cookie = new Cookie(req ,res ,{})
cookie.set('token',accessToken{signed:false,secure:false,httpOnly:true})
//get the token back
const token = cookie.get('token',{signed:false})
console.log(token)

MERN OAuth Implementation does not redirect to the google login page

I'm currently running a webserver using the MERN stack, and I'm trying to get OAuth login working properly. However, when I click the "login with google" button, react loads the homepage (but the URL changes). Fetching the URL directly gets a 302 response from the server, but my front-end doesn't change.
Server.js
const express = require('express');
const path = require('path');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const logger = require('morgan');
const cors = require('cors');
const secure = require('express-force-https');
const passport = require('passport');
const cookieSession = require('cookie-session');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
const dbRoute = process.env.MONGODB_URI || 'NO DB ROUTE PROVIDED';
// db setup
mongoose.connect(
dbRoute,
{
useNewUrlParser: true,
useUnifiedTopology: true,
dbName: process.env.DATABASE_NAME,
}
);
let db = mongoose.connection;
db.once('open', () => console.log("Connected to the database"));
db.on('error', console.error.bind(console, "MongoDB connection error: "));
// middleware
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false })); // body parsing
app.use(bodyParser.json());
app.use(logger("dev"));
app.use(express.static(path.join(__dirname, "client", "build"))); // for serving up the clientside code
app.use(secure); // ensure that the connection is using https
app.use(cookieSession({ // cookies!
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
keys:['vcxzkjvasddkvaosd'] // yeah i'm sure that's secure enough
}));
// models
require('./models/rule');
require('./models/affix');
require('./models/user');
// passport security
require('./config/passport');
app.use(passport.initialize());
app.use(passport.session());
// routes
app.use(require('./routes'));
// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
app.listen(port);
console.log(`Server listening on ${port}`);
Route (There are a few index files in different folders, so the full path for this route it /api/user/google)
const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const User = mongoose.model('User');
router.get('/google',
passport.authenticate('google', {
scope: ['profile', 'email']
})
);
router.get('/google/callback',
passport.authenticate('google', { failureRedirect: '/affixes'}),
(req, res) => {
res.redirect('/?token=' + req.user.token);
}
);
Passport.js
const mongoose = require('mongoose');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
require('dotenv').config();
const User = mongoose.model('User');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id).then((user) => {
done(null, user);
})
});
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
callbackURL: '/api/user/google/callback',
proxy: true
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id })
.then((existingUser) => {
if (existingUser) {
done(null, existingUser);
} else {
new User({ googleId: profile.id }).save()
.then((user) => done(null, user));
}
});
}
));
Frontend login page (has a fetch button and a link button. As described above, different behavior)
import React from 'react';
import {
ComingSoon
} from '../Common';
import {
Button
} from '#material-ui/core';
const handleClick = () => {
fetch('/api/user/google')
}
export default function Login() {
return (
<>
<Button onClick={handleClick}>
Login with Google
</Button>
<button>Log in with Google</button>
</>
);
}
Update: Looks like some kind of CORS issue, although I still don't know how to fix it. Browser spits out
Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/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 an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Adding the requested header gives me
Access to fetch at '...' (redirected from 'http://localhost:3000/api/user/google') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
It turns out I was quite wrong about the nature of this issue! The problem was that my fetch requests to my OAuth endpoint were calling my frontend, not my backend because the request included text/html in its Accept header. Using the react advanced proxy setup to route to the proper URI fixed the issue.
See: https://github.com/facebook/create-react-app/issues/5103 and https://github.com/facebook/create-react-app/issues/8550

request session not persistent - express-session

I'm trying to have a session containing user data in the node.js/express FW.
I'm using express-session. I'm not using session store yet.
I have 2 pages in the client (angular) where I iterate between - Login and Dashboard. The idea is to create the session after successful login, then routing to the dashboard page. In the dashboard page I have an anchor with routinlink to the login:
<a [routerLink]="['/login']" >BackToLogin</a>
When navigating back to the loginPage (when activating a route), I execute a service with an end-point to the express server which check if the request has a session with a request in it (I expect it to be). The problem is that I see that the session is not the same session (the id changes)
See my code:
Node.js side - server.js file:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const session = require ('express-session');
var cookieParser = require('cookie-parser');
const SESS_NAME = 'sid';
app.use(session({
name:SESS_NAME,
key: 'user_sid',
resave:false,
saveUninitialized:false,
secure: process.env.NODE_ENV ==="production",
secret:'<some random text>',
cookie:{
httpOnly: true,
secure: process.env.NODE_ENV ==="production",
expires: 60000
}
}));
app.use(bodyParser.text());
app.use(bodyParser);
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cors()); //No limitation for test reasons
app.use(cookieParser());
//disabled on purpose
//var sessionManagement = require('./middleware/sessionManagement');
// API
app.use("/", require("./api/v1/routes.js"))//This file includes:
/*
const express = require('express');
const router = express.Router();
router.use("/login", require('./login'));
router.use("/session", require('./session'));
module.exports = router;
*/
...etc
app.listen(config.port, () => console.log(`Process ${process.pid}: Listening on port ${config.port}`));
login.js on the server: responsible for validating user and store user data in session:
const express = require('express');
const router = express.Router();
const schema = require('./objectSchemaJson.schema.json');
const scehmaCheck = require('../../middleware/checkForSchema')(schema);//this is
a schema check (middleware) - if suceeded continue (next)
const storeSession = (req, dataResult) =>
{
if (<dataResult return with valid use data>) //This is "where the magic happanes"
{
req.session.user = {
username: <get userName from dataResult>,
ID: <Get ID from dataResult>,
Role: <Get Role from dataResult>
}
}
}
router.use("/", scehmaCheck, (req, res, next) => {
return GetUserDataFROmDB(req.body).then((dataResult) => { //reaching the DB - not mentioned here on purpose
storeSession(req, dataResult); // This is where the session set with user data
res.status(200).json(dataResult);
}).catch((err) => {
next({
details: err
})
});
});
module.exports = router;
This is the end point on the server that responsible for getting the session - session.js - This is where the problem appears - the res.session has a session ID which is different that the one I created after the login
const express = require('express');
const router = express.Router();
hasSession : function(req, res) //This is where the problem appears - the res.session has a session ID which is different that the one I created after the login
{
if (req.session.user)
{
res.status(200).json(
{
recordsets: [{Roles: req.session.Roles, UserName: req.session.user.username}]
});
}
else{
res.status(200).json({});
}
}
router.use("/", (req, res, next) => { return sessionManagement.hasSession(req, res, next)});
module.exports = router;
Client side:
//HTML:
<div>
<label>Username:</label>
<input type="text" name="username" [(ngModel)]="userName" />
</div>
<div>
<label>Password:</label>
<input type="password" name="password" [(ngModel)]="password"/>
</div>
<div>
<button (click)="login()">Login</button>
</div>
//COMPONENT:
login()
{
this.srv.login(this.userName, this.password).subscribe(result =>
{
if (<result is valid>)
{
this.router.navigate(['/dashboard']);
}
}
);
}
//This reach the node.js endpoint and routing to the session.js end point - it is executes when the router-outlet activated in the app.component:
/*
onActivate(componentRef : any)
{
if (componentRef instanceof LoginComponent)
{
componentRef.getSession();
}
}
*/
getSession() : void
{
this.sessionService.getSession().subscribe( result =>
{
if (<result is valid>)
{
this.router.navigate(['/dashboard']);
}
});
}
I found a similar question on github - no solution yet:
https://github.com/expressjs/session/issues/515
but it might be a cookie <-> server configuration issue.
Found the problem - the root cause was that the client didn't send a cookie when making an httprequest.
2 things needed to be done in order to solve the problem:
1. CORS Definition
Set the CORS definition to creadentials: true along with the origin (the host name of the client, which is probably with a different port\hostname):
app.use(cors({
origin: config.origin,
credentials: true
}));
2. Set crendentials
For every http rest method (get and post, in my case) add withCredentials property with a value of true:
return this.http.get<any>(<path>, { withCredentials: true })
or
return this.http.post<any>(<path>, <body>, { withCredentials:true })

Resources