My index.js Server
// USE STRICT;
const express = require('express');
const app = express();
const session = require('express-session');
const http = require('http').Server(app);
const socket = require('socket.io');
const schedule = require('node-schedule');
const cors = require('cors');
const io = socket(http, {
cors: {
origin: 'http://localhost:8080',
methods: ['GET', 'POST'],
allowedHeaders: ['my-custom-header'],
credentials: true
}
});
const port = 8080;
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/uploads'));
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const mustacheExpress = require('mustache-express');
app.engine('html', mustacheExpress());
app.set('view engine', 'html');
app.set('views', __dirname + '/views');
const secret = 'somesecretkeyhere';
const passport = require('passport');
const helmet = require('helmet');
const { sendMail } = require('./controllers/sellerAdsController');
// Gives us access to variables set in the .env file via `process.env.VARIABLE_NAME` syntax
// require('dotenv').config();
// Must first load the models before passport
require('./models/user');
// Pass the global passport object into the configuration function
require('./config/passport')(passport);
// This will initialize the passport object on every request
app.use(passport.initialize());
// Allows our remote applications to make HTTP requests to Express application
app.use(cors());
app.use(helmet());
app.use(express.urlencoded({ extended: false }));
// app.use(express.json()); //WARNING: Do not turn on. stops formidable for api calls
app.use(cookieParser(secret));
app.use(session({
secret: secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true
}
}));
app.use(csrf());
// Stop page caching
app.use(function (req, res, next) {
res.set('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
next();
});
// Imports all of the routes from ./routes/index.js
app.use(require('./routes/api/v1'));
// Socket Operations
// io.on('connection', io => {
// let sessionId = io.id;
// io.on('clientHandshake', (data) => {
// console.log(data);
// io.emit('serverHandshake', { sessionId: sessionId });
// });
// });
// io.use((socket, next) => {
// const username = socket.handshake.auth.username;
// if (!username) {
// return next(new Error('invalid username'));
// }
// console.log(username);
// socket.username = username;
// next();
// });
io.on('connection', (socket) => {
console.log('👾 New socket connected! >>', socket.id);
// notify existing users
socket.broadcast.emit('user connected', {
userID: socket.id,
username: socket.username,
});
socket.on('private message', ({ content, to }) => {
socket.to(to).emit('private message', {
content,
from: socket.id,
});
console.log(content, to);
});
});
// EROOR HANDLING ROUTES MUST BE BENEATH ALL APP.USE AND ROUTES
// Check if request is from web or app (HTML/JSON)
// Handle 404
app.use(function (req, res) {
res.status(404);
res.render('404.html', { title: '404: File Not Found' });
});
// Handle 500
app.use(function (error, req, res) {
return res.send(error);
// res.status(500);
// res.render('500.html', { title: '500: Internal Server Error', error: error });
});
// SCHEDULED JOBS
const now = new Date();
let date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0, 0);
schedule.scheduleJob(date, sendMail);
http.listen(port, () => {
console.log(`listening on *:${port}`);
});
And this is how I am getting from VUE
window.axios.get('/databank/getCSRF').then((response) => {
window.axios.defaults.headers.common['XSRF-TOKEN'] = response.data;
}, (err) => {
console.log(err)
})
And this is my login request header
XSRF-TOKEN from my login request header sent by axios
So Ive set my server up like that, and my vue SPA, but getCSRF() seems to be getting the request but I can't do a POST request back to the server throws an error
ForbiddenError: invalid csrf token
at csrf
Maybe because you wrote XSRF-TOKEN instead of CSRF-Token as it suggests in the Express Documentation.
Related
I'm working with a back-end made with Node.js and front-end made with Angular.
I use express-sessions for login, at first, it returns the correct req.session.usuario. But in the next POST request for checking if the user is logged in it returns undefined.
Here is the app.js of node
var express = require('express');
var path = require('path');
var logger = require('morgan');
var cors = require('cors');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
var usersRouter = require('./routes/users');
var instructorRouter = require('./routes/instructor');
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser());
app.use(cors( {
credentials: true,
origin: 'http://localhost:4200'
}));
app.use(session({
secret: "Shh, es un secreto",
resave: false,
saveUninitialized: true
}))
app.use('/', indexRouter);
app.use('/auth', authRouter);
// app.use('/usuario', authMiddleware.estaLogueado ,usersRouter);
app.use('/usuario', usersRouter);
app.use('/instructor', instructorRouter);
...
module.exports = app;
This is my function for the route in the auth.js
router.post('/login', (req, res, next) => {
const username = req.body.username.toLowerCase();
Usuario.findOne({
where: {
username: username
}
}).then((usuario) =>{
if (usuario) {
bcrypt.compare(req.body.password, usuario.password, function(errp, result) {
if(result){
req.session.usuario = usuario.id; // Saves the session
console.log("La sesion " + req.session.usuario);
res.json({username: usuario.username, color: usuario.color});
} else {
next(new Error("Contraseña incorrecta"));
}
});
}else{
next(new Error("Usuario invalido"));
}
}, err => {
next(new Error("Usuario invalido: " + err));
});
});
And this is the function that I use for checking if is logged in:
router.post('/logged', (req, res) => {
console.log("intento: " + req.session.usuario) // here it shows undefined
req.session.usuario ? res.json({loggedIn: true}) : res.json({loggedIn: false});
})
In Angular these are the two functions that I use, the first one is the login (which works) and the second one is the logged that returns false all the time:
login(user, pass) {
return this.http.post(server + '/auth/login',
{username: user, password: pass},
{withCredentials: true}
)
}
logged(){
return this.http.post(server + '/auth/logged', {withCredentials: true})
}
In the actual component I do:
this.authservice.logged().subscribe( (data) => {
this.loggedIn = data['loggedIn'];
console.log(data['loggedIn']);
});
What is the problem here?
I finally got the answer! I was doing everything ok but the second request was actually not including the withCredentials json as options, it was passed as body so I added empty brackets to simulate the body and now it works like a charm!
I get the following error when I try to authenticate with passport. I wonder if I am missing some middleware or something. What ever the case, this just is not working at the moment. I am getting the following error on the backend:
] { message: 'You should provide access_token' }
[0] Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
App.js:29 POST http://127.0.0.1:3000/api/users/auth/facebook 401 (Unauthorized)
I have made sure that the access token prints on the frontend:
this is ther token blob {"access_token":"EAAGTZAFMSr8YBACcmZAaEaEMTbCKICwJqiohySMxb1pPrJaaECiPqfOqiPFln4hp2pucvSm9Pr42twDQVZBt4KZAIoEENaAINHVBcfZB8YdRM23Y9VjDSeSnUtOsyynufjCBdQyNozpI2N4bTJotZAEmLETjIqLZBxwP9VxBdDFZAYWMofEiZCUDrQwGk7fBahs8SEtzTB80kfAZDZD"}
Here is the frontend
facebookResponse = (response) => {
const token= JSON.stringify({access_token: response.accessToken}, {type : 'application/json'});
console.log('this is ther token blob', token);
const options = {
method: 'POST',
body: token,
mode: 'cors',
cache: 'default'
};
fetch('api/users/auth/facebook', options).then(r => {
const token = r.headers.get('x-auth-token');
if(token){
try {
r.json().then(user => {
if (token) {
this.setState({isAuthenticated: true, user, token})
}
});
} catch{
console.log('error');
}
}
})
};
Here is the backend
var express = require('express');
var router = express.Router();
var { generateToken, sendToken } = require('./token.utils');
var passport = require('passport');
var config = require('./config');
var request = require('request');
require('../../passport')();
router.use(passport.initialize());
router.use(passport.session());
router.post('/auth/facebook',
function(req, res, next){
passport.authenticate('facebook-token', function(error, user, info){
console.log(error);
console.log(user);
console.log(info);
if (error) {
res.status(401).send(error);
} else if (!user) {
res.status(401).send(info);
} else {
next();
}
res.status(401).send(info);
})(req, res);
});
module.exports = router;
Here is my jwt strategy I am using facebook login:
require('./mongoose')();
var passport = require('passport');
var User = require('mongoose').model('User');
var FacebookTokenStrategy = require('passport-facebook-token');
module.exports = function () {
passport.use(new FacebookTokenStrategy({
clientID: '443534076456---',
clientSecret: 'd9c12cd1c8c7fcea7abb391a0bbb---'
},
function (accessToken, refreshToken, profile, done) {
User.upsertFbUser(accessToken, refreshToken, profile, function(err, user) {
return done(err, user);
});
}));
};
Here is the main server file:
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const passport = require('passport');
const path = require('path');
const users = require('./routes/api/users');
const cors = require('cors');
const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
var corsOption = {
origin: true,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
exposedHeaders: ['x-auth-token']
};
app.use(cors());
// const db = require('./config/keys').mongoURI;
// mongoose.connect("mongodb://user1:Voodoo12#ds263146.mlab.com:63146/fb-login-chat").then(()=> console.log('MongoDb Connected'))
// .catch(err=> console.log(err));
app.use(express.static(path.join(__dirname, 'public')));
app.use(passport.initialize());
app.use(passport.session());
app.use('/api/users', users);
//serve static assets if in production
// if(process.env.NODE_ENV === 'production'){
// //Set a static folder
// app.use(express.static('client/build'))
// app.get('*', (req, res)=>{
// res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
// });
// }
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}`));
I thinks your API is not able to decode the payload and extract your access token
try to add headers in yours options for the request
const options = {
method: 'POST',
body: token,
mode: 'cors',
cache: 'default',
// add headers
headers: { 'Content-type': 'application/json' }
};
Regards,
MA
I currently have a React App (via create-react-app) and an Express server for my API calls. They are both running separately and have been working fine until I ran into this problem:
I'm also using express-session and passport for user authentication. I'm able to authenticate users but the session doesn't persist between API calls. If I successfully login, the next time I make an API call, req.sessionID will be different, and req.isAuthenticated() will return false.
Here is my server code:
'use strict'
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var mongoose = require('mongoose');
var passport = require('passport');
var session = require('express-session');
var cors = require('cors');
var flash = require('connect-flash');
var store = require('./store');
var database = require('./database');
var app = express();
var port = process.env.port || 3001;
var router = express.Router();
var promise = mongoose.connect('mongodb://localhost:27017/lifeless-db', {useMongoClient: true});
//app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser('lifeless-secret-01890'));
app.use(session({
secret: 'lifeless-secret-01890',
saveUninitialized: true,
cookie: {
secure: false,
}
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
// Initialize Passport
var authinit = require('./auth/init');
authinit(passport);
// For debugging:
app.use(function(req, res, next) {
console.log("SessionID: " + req.sessionID);
console.log(req.isAuthenticated() ? "This user is logged in" : "This user is NOT logged in");
next();
});
// GET
app.get('/items', function(req, res) {
store.getAllItems((items) => {
if(!items) return res.send({error: true, message: 'Error loading store :/', items: null});
else return res.send({error: false, message: 'Success', items: items});
});
});
app.get('/login', function(req, res) {
console.log(req.flash());
console.log("Login fail");
res.send({error: true, message: 'Unknown error'});
});
// POST
app.post('/message', function(req, res) {
database.submitMessage(req.body.email, req.body.message, (success) => {
if (success) return res.send({error: false, message: 'Success'});
else return res.send({error: true, message: 'Error sending message'});
});
});
app.post('/login', passport.authenticate('local', {failureRedirect: '/login', failureFlash: true}), function(req, res) {
console.log(req.flash());
console.log("Login success");
return res.send({error: false, message: 'Success'});
});
// SERVER
app.listen(port, function(){
console.log('Server started.');
console.log('Listening on port ' + port);
});
And here is an example of an API call from the React App (using axios):
login(e) {
e.preventDefault();
axios({
method: 'post',
url: 'http://localhost:3001/login',
data: {
username: this.state.username,
password: this.state.password,
}
})
.then((response) => {
console.log(response.data);
if (response.data.error) {
this.setState({error: true, errmessage: response.data.message});
} else {
this.setState({redirect: true});
}
})
.catch((error) => {
this.setState({error: true, errmessage: 'Error logging in'});
});
}
I figure there must be some way to have React store the session somehow (?) but I'm fairly new to React and don't really know how to use it with an express backend.
Your axios request from your React client needs to be sent withCredentials. To fix it, either do axios.defaults.withCredentials = true; or do axios.get('url', {withCredentials: true})...Also in your expressjs backend, set cors options credentials: true
Here is an example of setting up express-session using connect-redis. First, setup both express-session and the Redis store.
var session = require('express-session);
var RedisStore = require('connect-redis')(session);
Then, where you're declaring middleware for your app;
app.use(session({
store: new RedisStore({
url: process.env.REDIS_URL
}),
secret: process.env.REDISSECRET,
resave: false,
saveUninitialized: false
}));
now when you use req.session.{etc} this writes to your Redis DB.
I have some problems with the express session where I cannot retrieve my session variable that I had stored previously. Below are parts of my codes that I had written.
server.js
let express = require('express'),
path = require('path'),
bodyParser = require('body-parser'),
cors = require('cors'),
config = require('./config/database'),
expressSession = require('express-session'),
uid = require('uid-safe'),
db;
let app = express();
//Import Routes
let auth = require('./routes/auth'),
chimerListing = require('./routes/chimer-listing'),
brandListing = require('./routes/brand-listing');
//Specifies the port number
let port = process.env.PORT || 3000;
// let port = 3000;
// Express session
app.use(expressSession({
secret: "asdasd",
resave: true,
saveUninitialized: false,
cookie: {
maxAge: 36000000,
secure: false
}
}));
//CORS Middleware
app.use(cors());
//Set Static Folder
var distDir = __dirname + "/dist/";
app.use(express.static(distDir));
//Body Parser Middleware
app.use(bodyParser.json());
//MongoDB
let MongoClient = require('mongodb').MongoClient;
MongoClient.connect(config.database, (err, database) => {
if (err) return console.log(err)
db = database;
//Start the server only the connection to database is successful
app.listen(port, () => {
console.log('Server started on port' + port);
});
});
//Make db accessbile to routers;
app.use(function(req, res, next) {
req.db = db;
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.set('Access-Control-Allow-Headers', 'Content-Type');
next();
});
//Routes
app.use('/login', auth);
app.use('/user-listing', userListing);
app.use('/brand-listing', brandListing);
//Index Route
app.get('/', (req, res) => {
res.send('Invalid Endpoint');
});
genuuid = function() {
return uid.sync(18);
};
auth.js
let express = require('express'),
router = express.Router(),
db;
//Login Router for chimer
router.post('/chimer', (req, res, next) => {
db = req.db;
// let client = req.client;
db.collection('chimeUser').find({
Username: req.body.username,
Password: req.body.password
}).toArray().then(function(docs) {
//If there is such user
if (docs.length >= 1) {
req.session.chimerId = docs[0]._id;
console.log(req.session);
req.session.save(function(err) {
// session saved
if (err)
console.log(err)
res.json({
success: true,
chimerId: docs[0]._id
//objects: docs
});
})
} else {
res.json({
success: false,
//objects: docs
})
}
});
});
//Login Router brand
router.post('/brand', (req, res, next) => {
db = req.db;
db.collection('brand').find({
Username: req.body.username,
Password: req.body.password
}).toArray().then(function(docs) {
req.session.brand = docs;
console.log(req.session.brand);
//If there is such user
if (docs.length >= 1) {
res.json({
success: true,
//objects: docs
})
} else {
res.json({
success: false,
//objects: docs
})
}
//db.close()
});
});
});
module.exports = router;
user-listing.js
let express = require('express'),
moment = require('moment'),
router = express.Router(),
// ObjectID = require('mongodb').ObjectID,
db, client;
// let applyListing = require('../models/chimer-listing');
//Retrieve All Listing
router.get('/getAllListing', (req, res, next) => {
db = req.db;
console.log(req.session)
db.collection('listing').find().toArray().then(function(listing) {
//If there is any listing
if (listing.length >= 1) {
res.json({
success: true,
results: listing
})
} else {
res.json({
success: false,
})
}
//db.close()
});
});
module.exports = router;
So in my server.js, I have three routes file which is auth, user-listing, and brand-listing.
Firstly, a user will need to login with the web application which is developed in angular2 and this will trigger the auth route. It will then check for the credentials whether does it exist in the database if it exists I will then assign an ID to req.session.chimerId so that in other routes I will be able to use this chimerId.
Next, after the user has logged in, they will then retrieve an item listing. The problem arises where I can't seem to retrieve the req.session.chimerId that I had previously saved. It will be undefined
NOTE: I tried this using Postman and the browser. In the Postman it works, I am able to retrieve back the req.session.chimerId whereas when I use the angular2 application to hit the endpoints req.session.chimerId is always null
I am trying to implement csrf protection into my project but I can't make it work with jQuery Ajax. (It works with normal posts requests, though)
If I tamper the token using chrome dev tools before I send the form, I still see "data is being processed" text rather than invalid csrf token error.
app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var router = express.Router();
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//app.set('strict routing', true);
app.set('view options', {layout: false});
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });
app.use(express.static(path.join(__dirname, 'public')));
app.get('/form', csrfProtection, function(req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/form', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function (err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
send.jade
html
head
meta(name='_csrf', content='#{csrfToken}')
body
form(action='/form', method='POST')
| Favorite color:
input(type='text', class="favori", name='favoriteColor')
button(type='submit') Submit
script(src="javascripts/frontend/jquery/jquery-3.0.0-alpha1.js")
script(src="javascripts/test.js")
test.js
$(document).ready(function () {
$.ajaxSetup({
headers: {'X-CSRF-Token': $('meta[name="_csrf"]').attr('content')}
});
$('button').click(function (e) {
e.preventDefault();
var text = $('.favori').val();
alert(text);
$.post(
"/form",
{
text: text
}, function (data) {
console.log(data);
});
});
});
Send the CSRF token inside the payload message:
$('button').click(function (e) {
e.preventDefault();
var text = $('.favori').val();
alert(text);
$.post(
"/form",
{
text: text,
_csrf : $('meta[name="_csrf"]').attr('content')
}, function (data) {
console.log(data);
});
});
To facilitate your work I think you can create a Jquery plugin to do it, something like this:
(function( $ ) {
$.postCSRF = function(to, message, callback) {
message._csrf = $('meta[name="_csrf"]').attr('content');
$.post(to, message, callback);
};
}( jQuery ));
// Usage example:
$.postCSRF('/form',{text:'hi'},function(res) {
console.log(res);
});
Example: http://jsfiddle.net/w7h4Lkxn/
Check the header to see if its passing the tampered token in the cookie or as part of the form data. It looks like your setup is for using cookies. So changing it on the form shouldn't affect the cookie. Lemme know if that helps reveal the issue.
your doing everything exactly right but you have to disable checking for the cookie entirely!
var csrfProtection = csurf({ cookie: false });
the author mentions it here
https://github.com/expressjs/csurf/issues/52
thanks for the above code with the header post as this was crucial for validating the express session token and i have shared it with others