I am new to node.js and angular4 and am working on an application which required user authentication. I want to save the user role in a session in node.js and want to access that session in multiple routes. However, I am unable to get the role in other routes. I have checked many answers regarding the same but none have worked for me so far.
Scenario:
I have implemented Express-Session in app.js
const session = require('express-session');
const cookieParser = require('cookie-parser')
const app = express();
const port = 4000;
app.use(cors());
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.all('*',function(req, res, next){
res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header("Access-Control-Allow-Credentials", "true");
next();
});
app.use(cookieParser())
app.use(session({secret: "my secret", resave: false, saveUninitialized: true, cookie: { secure: !true }}));
I have a file named user.js which is being used for login user. After successful verification, user information is stored in a session.
res.json({
success: true,
token: 'JWT ' + token,
user: {
id: user[0].UserId,
FirstName: user[0].FirstName,
MiddleName: user[0].MiddleName,
LastName: user[0].LastName,
EmailID: user[0].EmailID,
PhoneNo: user[0].PhoneNo,
Role: user[0].Role,
DistrictId: user[0].DistrictId
},
});
req.session.userRole = user[0].Role;
req.session.save();
At this point, req.session.userRole has the user role.
However, when I use req.session.userRole in another route, eg: dept.js, it shows undefined.
Also, when I use Chrome app: Postman, it displays
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false },
userRole: 'ADMN' }
But when I run the application which is using Angular4, it just shows
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true,
secure: false }
PS: Node.js app is on port: 4000 and angular4 app is on 4200.
Thanks in Advance.
Related
I have the problem that on production (Apache server/MERN stack) certain cookies are not accessible from the browser with document.cookie.
On localhost, both front-end and back-end are on the same domain, namely localhost, and just use different port numbers. Because they are working on the same domain, they share cookies.
On production, however, front-end and back-end operate on different (sub)domains. As a result, they don't have access to each other's cookies.
How can I make the cookies set by the back-end accessible for the front-end, also on production?
I thought this should be done with CORS, and with httpOnly: false and sameSite: 'none'. But the Node setup below doesn't work. In the browser I'm unable to access from the front-end the cookies set by the back-end.
var cors = require("cors");
const session = require("express-session");
const csurf = require("csurf");
const cookieParser = require("cookie-parser");
var corsOptions = {
origin: process.env.CORS_ORIGIN_URL.split(","), // the front-end is included here.
credentials: true,
exposedHeaders: ["set-cookie"],
};
app.use(cors(corsOptions));
let sessionSettings = {
secret: process.env.SESSION_SECRET,
key: process.env.SESSION_KEY,
store: sessionStore,
resave: true,
saveUninitialized: true,
cookie: {
secure: false,
},
};
app.use(session(sessionSettings));
const protect = csurf({
cookie: true,
httpOnly: false,
sameSite: 'none'
});
app.use(
cookieParser("test", {
sameSite: 'none',
httpOnly: false,
secure: false,
maxAge: 900000,
})
);
app.use((req, res, next) => {
protect(req, res, next);
});
app.use((req, res, next) => {
if (req.csrfToken) {
res.cookie(
"XSRF-TOKEN",
req.csrfToken(),
{
secure: true,
httpOnly: false,
sameSite: 'None'
}
);
}
next();
});
app.use(`${process.env.API_PATH}/csrf`, (req, res) => {
return res.status(200).json({
status: true,
csrfToken: req.csrfToken(),
});
});
...
Here you need to share the cookie with subdomains and main domain. You can do this by adding a domain field in res.cookie options. Now your main domain and subdomains can access this cookie.
res.cookie(
"XSRF-TOKEN",
req.csrfToken(),
{
secure: true,
httpOnly: false,
sameSite: 'None',
domain: 'mydomain.com'
}
);
I'm using the passport.js local strategy.
I was testing it under proxy setting, localhost.
Things worked fine until I prepare to deploy.
I changed the API address to include dotenv and set CORS settings on the server-side.
When trying to login, CORS works fine, OPTIONS and the POST get 200 ok. The client receives the success data. cookie saved in client.
But when auth checking process runs right after Redux "isLoggedin" state is updated(useEffect), req.session doesn't
t have the passport object. So deserializeUser not be called. The session contains other cookie info except for Passport.
This one is only on Firefox(not Chrome): Page will be redirected if the login auth succeeded(it checks right after login redux state changed), but since it's failed, the user stays on the login page still. But if I try to login on the same page again, the cookie start to show the passport object.(in other words, it shows from 2nd attempt). But it doesn't persist because the Redux login state has been changed to true at the first login attempt already.(so Auth checking doesn't occur.)
client:
Axios.post(
`${process.env.REACT_APP_API_URI}/api/users/login`,
loginData,
{ withCredentials: true, }
).then((res) => res.data){
//save isLoggedin to true in Redux
}
// auth check logic starts right after changing isLoggedin. Axios Get to authenticateCheck
server.js
app.use(helmet());
// app.use(express.static('public'));
app.use("/uploads", express.static("uploads"));
// Passport configuration.
require("./utils/passport");
// connect to mongoDB
mongoose
.connect(db.mongoURI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
})
.then(() => console.log("mongoDB is connected."))
.catch((err) => console.log(err));
// CORS Middleware
const corsOptions = {
origin: "http://localhost:8000",
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
credentials: true,
methods: ["POST", "GET", "DELETE", "PUT", "PATCH", "OPTIONS"],
allowedHeaders:
"Origin, X-Requested-With, X-AUTHENTICATION, X-IP, Content-Type, Accept, x-access-token",
};
// app.use(cors(corsOptions));
app.options(/\.*/, cors(corsOptions), function (req, res) {
return res.sendStatus(200);
});
app.all("*", cors(corsOptions), function (req, res, next) {
next();
});
// to get json data
// support parsing of application/json type post data
app.use(express.json());
app.use((req, res, next) => {
req.requestTime = new Date().toISOString();
next();
});
//support parsing of application/x-www-form-urlencoded post data
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// db session store
const sessionStore = new MongoStore({
mongoUrl: db.mongoURI,
collection: "sessions",
});
// tell app to use cookie
app.use(
session({
secret: process.env.SESSION_SECRET_KEY,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
httpOnly: true,
secure: false,
sameSite:"none",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
//keys: [process.env.COOKIE_ENCRYPTION_KEY]
},
name: "pm-user",
})
);
// tell passport to make use of cookies to handle authentication
app.use(passport.initialize());
app.use(passport.session());
app.use(compression());
app.use(flash());
app.use((req, res, next) => {
console.log("req.session:", req.session);
// console.log('/////// req: ///////', req);
console.log("////// req.user: ", req.user, " //////");
next();
});
//---------------- END OF MIDDLEWARE ---------------------//
authController:
exports.authenticateCheck = (req, res, next) => {
console.log("req.isAuthenticated():", req.isAuthenticated());
if (req.isAuthenticated()) {
return next();
} else {
return res.json({
isAuth: false,
error: true,
});
}
};
It would be a really big help if you can advise me where to look to fix it.
Thanks.
I found the solution finally.
It was because the session was renewed every time when a new request starts other than a login request.
The solution was, I had to add { withCredentials: true } to every Axios option in my frontend.
I am able to log in and out with POSTMAN through the heroku hosted node API.
In my react application, the API call with AXIOS (withcredentials:true) does not set the passport cookies, causing the session to not persist. Localhost react and Localhost server does not have this problem.
HEROKU SERVER SIDE, I have the following code:
app.use(cors({
origin: "http://localhost:3000",
credentials: true
}));
app.enable('trust proxy');
mongoose.connect(dbconfig.url, {
useUnifiedTopology: true,
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false });
// Parse URL-encoded bodies (as sent by HTML forms)
app.use(express.urlencoded({extended: true}));
app.use(cookieParser('street'));
app.use(session({
secret: 'katsura street',
resave: true,
saveUninitialized: true,
proxy: true,
store: new MongoStore({ mongooseConnection: mongoose.connection },)
}));
app.use(passport.initialize());
app.use(passport.session());
I have checked that the cookie parser is above the session,
the session is initialized before passport.
Is my cors affecting my local reactapp? instead of localhost, am I supposed to reference my local computer's external API?
REACT SIDE:
Axios.post(Config.ServerURL + 'auth/login',
{email: this.state.email, password: this.state.password},
{
withCredentials: true
}).then(result => {
console.log(result.data);
}).catch(error => {
//make sure message doesn't crash
if (error.response !== undefined) {
try {
console.log(error.response);
this.setState({message: error.response.data.info.message})
}catch(error) {
console.log(error);
}
}
});
After checking the headers and seeing that the cookies are sent but filtered out, I came to a article: heroku blog
After 2019, Chrome and other browsers have cross domain cookies disabled to prevent CSRF attacks.
In my use case, I can just turn it off with the following on the Node API server:
app.use(session({
secret: 'street',
resave: true,
saveUninitialized: true,
proxy: true,
cookie: {
sameSite:'none',
secure:true
},
store: new MongoStore({ mongooseConnection: mongoose.connection },)
}));
changing samesite to none, will allow chrome to set your cookies with cross domains.
In my case, it was just setting, in the session settings, cookie.sameSite to "none", and proxy to true.
I have 2 web application in same IP and different port:
app1 = (site.com) -> 66.88.66.88:5000
app2 = (login.site.com) -> 66.88.66.88:5001
I`m using Nginx and set 2 proxy for these 2 application. The users have to login in to app1 and system will set a session for logged in user. after that system will redirect user to app2 and I need access to logged in user session in appp2.
I store my session in mongodb in same collecton that both apps has access to that.
My problem is I can`t have access to loggedin user session in app2.
This is my session settings:
app1:
const MongoStore = require('connect-mongo')(session);
mongoose.Promise = require('bluebird');
const connection = mongoose.createConnection(config.dbhost, function(err){
if(err){
console.log(err);
} else {
console.log('connected to the database successfuly.');
}
});
/* Session config */
var expiryDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
app.use(session({
secret: config.secureHasherKey ,
resave: true,
saveUninitialized: false,
cookie: {
secure: false,
httpOnly: true,
domain: 'site.com',
path: '/',
expires: expiryDate
},
store: new MongoStore({ mongooseConnection: connection })
}));
app2:
const MongoStore = require('connect-mongo')(session);
mongoose.Promise = require('bluebird');
const connection = mongoose.createConnection(config.dbhost, function(err){
if(err){
console.log(err);
} else {
console.log('connected to the database successfuly.');
}
});
/* Session config */
var expiryDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
app.use(session({
secret: config.secureHasherKey ,
resave: true,
saveUninitialized: false,
cookie: {
secure: false,
httpOnly: true,
domain: 'login.site.com',
path: '/',
expires: expiryDate
},
store: new MongoStore({ mongooseConnection: connection })
}));
And this is my CORS settings:
app.all("*", function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Cache-Control, Pragma,
Origin, Authorization, Content-Type, X-Requested-With");
res.header("Access-Control-Allow-Methods", "GET, PUT, POST");
return next();
});
How can I find the problem?
I have a suite of programs that are all under the same company and I am trying to develop a single login / authentication service that can persist through all of the programs. The idea is very micro-service oriented in which we will have one service to handle authentication and persist it as long as someone is in one of the programs. The issue is I need my other services to be able to access the same cookies across all of the domains and be able to send those cookies to the auth service for session verification. Please correct me if this is not the proper way to set up micro-services with a login/auth service.
For my front end (Angularjs):
service.login = function (obj, callback) {
$http.post(loginService + "login", obj, {
withCredentials: true
}).success(function (data) {
callback(data);
})
.error(function (data, status, headers) {
console.log(status);
});
};
For my server (Node, Express, Mongo):
var options = {
pfx: fs.readFileSync('company.pfx'),
passphrase: 'pass',
ca: [fs.readFileSync('gd1.crt'), fs.readFileSync('gd2.crt'), fs.readFileSync('gd3.crt')],
spdy: {
protocols: ['h2', 'spdy/3.1', 'http/1.1'],
plain: false,
'x-forwarded-for': true,
connection: {
windowSize: 1024 * 1024, // Server's window size
// **optional** if true - server will send 3.1 frames on 3.0 *plain* spdy
autoSpdy31: false
}
}
};
var server = spdy.createServer(options, app);
app.use(helmet());
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use("/static", express.static('static'));
app.use("/logins", express.static('logins'));
app.set('trust proxy', 1) // trust first proxy for production with a proxy server
app.use(session({
resave: false,
saveUninitialized: true,
genid: function (req) {
return uuid.v4() // use UUIDs for session IDs
},
name: "myToken",
secret: 'mysecret',
cookie: { secure: false, maxAge: (45 * 60000) }, // set secure to true when in production
store: new mongoStore({ url: 'mongodb://' + base + 'sessions' })
}));
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);//req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH');
res.header('Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version');
next();
});
Requesting:
app.get('/', function (req, res) {
var sess = req.session, token = req.cookies.myToken;
res.send('Hello World!');
});
To test this I have a virtual machine running on my system with the application deployed and then I am also running my localhost:/ application. From my understanding my cookies should remain the same between the two calls with the same session if I have CORS set up properly. Any help or suggestions?
Have you tried
res.header('Access-Control-Allow-Origin', '*.domain') ?
Basically a wildcard matching any subdomain under your main domain.