Looked through and tried everything I could find on here, and elsewhere by Googling...and I'm just not able to get past this. I'm using Node, Express, EJS, and attempting to use csurf on a form, that is posted w/ jQuery ajax. No matter how I configure csurf, I get "403 (Forbidden) invalid csrf token"
I've tried configuring both globally in app.js and in the controller. Here's what I tried in app.js:
var express = require('express');
var session = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var flash = require("connect-flash");
var csrf = require("csurf");
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(session({
secret: 'somethingsecret',
resave: true,
saveUninitialized: true,
httpOnly: true,
secure: false
}));
app.use(csrf());
app.use(function (req, res, next) {
var token = req.csrfToken();
res.cookie('XSRF-TOKEN', token);
res.locals.csrfToken = token;
console.log("csrf token = " + token);
next();
});
app.use(flash());
app.use(express.static(path.join(__dirname, 'public')));
app.use(function (err, req, res, next) {
if (err.code !== 'EBADCSRFTOKEN') return next(err);
// handle CSRF token errors here
res.status(403);
res.send('form tampered with');
})
//routing
var routes = require('./routes/index');
var users = require('./routes/users');
var register = require('./routes/register');
app.use('/', routes);
app.use('/users', users);
app.use('/register', register);
...with this controller:
var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var userSvc = require("../service/userservice");
var jsonParser = bodyParser.json();
router.get("/", function(req, res, next) {
console.log("token = " + token);
userSvc.getAllPublicRoles(function(data) {
res.render("register", {
title: "Register a new account",
roles: data
});
});
});
router.post("/new", jsonParser, function(req, res, next) {
userSvc.addUser(req.body, function(result) {
console.log("New user id = " + result.insertId);
res.send('{"success" : "Updated Successfully", "status" : 200}');
});
});
...and this view:
form:
<form id="registerForm" class="form-horizontal" method="post">
<input type="hidden" name="_csrf" value="<%= csrfToken %>" />
ajax call:
$.ajax({
url: "/register/new",
type: "POST",
dataType: "json",
data: user
}).done(function(data) {
if (data) {
console.log("Success! = " + data);
}
}).fail(function(data) {
console.log("Something went wrong: " + data.responseText);
});
Then I just tried just doing everything in the controller, removing all references, calls, etc. from app.js, and using the same form and ajax call as above:
var express = require("express");
var router = express.Router();
var bodyParser = require("body-parser");
var csrf = require("csurf");
var userSvc = require("../service/userservice");
var csrfProtection = csrf();
var jsonParser = bodyParser.json();
router.get("/", csrfProtection, function(req, res, next) {
var token = req.csrfToken();
console.log("token = " + token);
userSvc.getAllPublicRoles(function(data) {
res.render("register", {
title: "Register a new account",
csrfToken: token,
roles: data
});
});
});
router.post("/new", jsonParser, csrfProtection, function(req, res, next) {
userSvc.addUser(req.body, function(result) {
console.log("New user id = " + result.insertId);
res.send('{"success" : "Updated Successfully", "status" : 200}');
});
});
Not sure where to go from here. I've been using node for about two weeks, in my spare time, so pardon my ignorance here.
If you want to store the token in a cookie instead of the session, let csurf create the cookie for you e.g.
// Store the token in a cookie called '_csrf'
app.use(csrf({cookie: true));
// Make the token available to all views
app.use(function (req, res, next){
res.locals._csrf = req.csrfToken();
next();
});
Then you need to make sure the token is available when you're making the call using AJAX either via the POST'ed data, or as a custom request header such as 'xsrf-token'.
At the minute, you're providing the token to the form, but not the actual request (sent using AJAX).
For example, you could render the token in the AJAX setup:
$.ajaxSetup({
headers: {"X-CSRF-Token": "{{csrfToken}}" }
});
After several more hours of troubleshooting and searching, I found a post that helped answer it. All I needed was to pass the header value in the ajax post. Makes sense, I just overlooked it. Like so:
<input type="hidden" id="_csrf" name="_csrf" value="<%= csrfToken %>" />
...and then in jQuery:
$.ajaxSetup({
headers: {"X-CSRF-Token": $("#_csrf").val()}
});
An another approach over my personal project is to resend a new token when I sucessfully submit my form:
For example over my form (that does file upload) I have the follwing html:
<form id="upload_form" type="multipart/form-data" data-csrf="{{csrfToken}}" method="post" action="/data_assets">
<input id="excell_upload" type="file" style="visible:hidden" name="data_assets"/>
</form>
And on file change I trigger the upload like that:
$('#excell_upload').on('change',function(event){
event.preventDefault();
var formData = new FormData($("#upload_form")[0]);
$.ajax({
'type':$("#upload_form").attr('method'),
'data': formData,
'url': $("#upload_form").attr('action'),
'processData': false,
'contentType': false,
'mimeType': 'multipart/form-data',
'headers': {"X-CSRF-Token": $("#upload_form").attr('data-csrf') },
'beforeSend': function (x) {
if (x && x.overrideMimeType) {
x.overrideMimeType("multipart/form-data");
}
$('#trigger_upload').addClass('disabled');
},
'success':function(data){
$('#upload_form').attr('data-csrf',data.csrfToken)
},
'fail':function(){
},
'complete':function(){
$('#trigger_upload').removeClass('disabled');
}
});
});
As you notice I receive a new csrf token in order to be able to reuse my form for new submits. I regenerate the CSRF token like that:
app.post('/data_assets',function(req,res,next){
res.json({'csrfToken':req.csrfToken()});
});
other than adding the "X-CSRF-Token" to the header on post you want to disable cookies entirely!
var csrfProtection = csurf({ cookie: false });
the author mentions it here
https://github.com/expressjs/csurf/issues/52
cookie and session validation should not be combined -- although it is a bit misleading since he has combined cookie and session validation in his documentation:
https://github.com/expressjs/csurf#simple-express-example
Related
I've just started learning Express and Servers.
Problem
Just wanted to load another EJS page onto my localhost:4000/ path as a response after a POST request has been made for a form.
However, although I do get the response of the EJS page with the data from the req.body in the form from the client-side. I can't seem to get the page to load on the browser.
Any ideas? pls help
Express.js Server
let express = require('express');
let app = express();
let bodyParser = require('body-parser');
let path = require('path');
let fs = require('fs');
var urlencodedParser = bodyParser.urlencoded({extended:true});
app.use(express.json());
app.set('view engine', 'ejs');
app.use('/', express.static(__dirname + '/views'));
app.use('/', express.static(__dirname + '/views/partial'));
//Handling the GET request with the render of EJS file "index"
app.get('/', (req, res)=> {
res.render('index');
});
//Handling the POST request from the client, and sending the EJS file "createAccount" as a response
app.post('/', urlencodedParser, (req, res) => {
res.set('cache-control', 'max-age=0; private; no-cache');
res.render('createAccount', {data:req.body}); //
});
app.listen(4000, ()=>{
console.log('Port 4000 has been called');
});
EDIT:
I've included the JS file which I am using to make the POST request below.
document.querySelector('#btn').addEventListener('click', Master);
async function Master(){
console.log("Button clicked")
const username = document.querySelector("#username").value;
const password = document.querySelector("#password").value;
let results = {
"username": username,
"password": password
};
console.log(results);
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(results)
};
const post = await fetch('/', options);
};
I don't know if this is a correct question lol but is it ok to have the same "/" in get and post request? Why not put something in it.
app.post("/create")
then also change this
const post = await fetch('/create', options);
Is there a way to intercept a http call before the csurf validation. I have the below code
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
// create express app
var app = express()
// create api router
var api = createApiRouter()
// mount api before csrf is appended to the app stack
app.use('/api', api)
// now add csrf and other middlewares, after the "/api" was mounted
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(csrf({ cookie: true }))
app.get('/form', function (req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() })
})
app.post('/process', function (req, res) {
res.send('csrf was required to get here')
})
function createApiRouter () {
var router = new express.Router()
router.post('/getProfile', function (req, res) {
res.send('no csrf to get here')
})
return router
}
I want to log the CSRF token sent by the client for troubleshooting an error I am getting. But I was not able to find a way to intercept the request before it is sent for CSRF validation.
I was able to do it by adding the following code
var logToken = function (req, res, next) {
console.log('Token: ', res.get('x-xsrf-token');
next();
};
app.use(logToken);
I added this code before configuring the csrf token and after configuring the cookie parser.
I'm quite new to AJAX, so sorry for potential missunderstandings, but I'm not completely through that thing.
I'm trying a simple thing. I have a server.js file, which is my backend basically. Then I have a index.html and a script.js. That's all, so a very basic setup. Now, on my script.js, I'm getting some data (a mail address). Now I want to send that data to my backend (into the server.js) to work with it there. How can I do this?
I found some posts already about AJAX with node.js, but I don't get it, especially not where to receive it in my backend. I'm using express for the server by the way.
What I have in my script.js is:
$.ajax({
type: "POST",
url: "server.js",
data: { mail: mail },
success: function(data) {
},
error: function(jqXHR, textStatus, err) {
alert('text status '+textStatus+', err '+err)
}
});
Right so far? How can I now receive the information in my server.js?
There's not much in so far, just:
var express = require('express');
var app = express();
var server = app.listen(3000);
app.use(express.static('public'));
Thanks for any help :)
Note: This was written before the question was updated with the code so the field names and port numbers that I used here as examples may need to be updated with the correct values.
Client-side code - example with jQuery:
$.post('/email', { address: 'xxx#example.com' });
(this can take optional callbacks and it returns a promise that can be used to add a success/error handler)
Server-side code - example with Express:
const express = require('express');
const bodyParser = require('body-parser');
const dir = path.join(__dirname, 'public');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/email', (req, res) => {
// you have address available in req.body:
console.log(req.body.address);
// always send a response:
res.json({ ok: true });
});
app.use(express.static(dir));
app.listen(4443, () => console.log('Listening on http://localhost:4443/'));
This assumes that your static files (HTML, client-side JavaScript, CSS) are in the public directory relative to your server.js file.
See this for background on the JSON/form-encoding issue:
Which method is prefer when building API
See this for background on serving static files:
How to serve an image using nodejs
That's actually quite simple to implement in Express.JS with the basic router:
I'm gonna give you the minified code snippets to help you get sense of how it works across browser and server.
in Front-End, you basically just want to "post" an email address to the backend:
$.post('/email', { email: 'howareyou#xx.com' })
and in Back-End(Express.JS), you should implement the basic router:
var express = require('express');
var app = express();
// use: app.METHOD(PATH, HANDLER)
app.post('/email/', function(req, res) {
var email = req.body.email
})
Read more here: http://expressjs.com/en/guide/routing.html
First, you need a valid route to hit when the server is running. You can do this in server.js through express.
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(express.static('public'));
app.post('/mail', function(req, res) {
var body = req.body;
console.log('email', body.email);
res.json({ message: 'I got the email!' });
});
var server = app.listen(3000);
Notice I have brought in an express middleware that will parse the body for JSON and make it available on the req object under req.body. You will need to install this dependency with npm install --save body-parser.
Then you need to send a POST request to that URL from the front-end.
$.ajax({
type: "POST",
url: "/mail",
data: { mail: mail },
success: function(data) {
console.log('message', data.message);
},
error: function(jqXHR, textStatus, err) {
alert('text status '+textStatus+', err '+err)
}
});
Now, if you submit an email, you should see a log in your terminal that shows the email and a log in your developer console in the browser that shows the message "I got the email!"
in server.js add this :
app.post('/searching', function(req, res){
//do something with req
});
and in script.js :
$.ajax({
type: "POST",
url: "/searching",
data: { mail: mail },
success: function(data) {
},
error: function(jqXHR, textStatus, err) {
alert('text status '+textStatus+', err '+err)
}
});
First of all you nedd to create a route for the Mail
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var app = express();
var router=app.Router();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false })); // Parse request body
app.use(express.static(path.join(__dirname, 'public')));
// Route to check Email
router.post('/CheckEmail',(req,res)=>{
var email=req.body.mail; // Get email here
})
app.listen(process.env.port || 3000,()=>{
console.log('server is running');
})
Ajax
$.ajax({
type: "POST",
url: "/CheckEmail", // post route name here
data: { mail: mail },
success: function(data) {
},
error: function(jqXHR, textStatus, err) {
alert('text status '+textStatus+', err '+err)
}
});
You need a few more things to actually be able to parse the body. Add this to your server.js file.
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
You need to specify a valid URL. Since you are listening on 3000. You also need to specify a route on your server as an endpoint.
$.ajax({
type: "POST",
url: "http:localhost:3000/",
data: { mail: mail },
success: function(data) {
},
error: function(jqXHR, textStatus, err) {
alert('text status '+textStatus+', err '+err)
}
});
Now you need to add a route on your server. You can do so by adding this to your server.js file after all of the app.use calls
app.post("/", function(req, res){
// your logic here
res.send("I am sending something back!");
})
I'm trying to login a CouchDB User into my express app via a frontend form and store the login in the session. What have so far is the following:
app.js:
var express = require('express');
var couchUser = require('express-user-couchdb');
var session = require('express-session');
var login = require('./routes/login');
var app = express();
app.use(couchUser({
users: 'http://localhost:5984/_users',
request_defaults: {
auth: {
user: 'admin',
pass: 'adminpw'
}
}
}));
app.use(session({ secret: 'secretstring'}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', login);
and in my login.js:
var express = require('express');
var router = express.Router();
var couchUser = require('express-user-couchdb');
/* GET users listing. */
router.get('/', function(req, res, next) {
res.render('login', {title: 'Login'});
});
router.post('/login', function(req, res) {
// Don't know what to put here
//res.send(req.body.username)
});
module.exports = router;
I don't know how to go on in my login.js route. Any help is appreciated.
Update - Since I couldn't get the code underneath to work because I didn't understand it completely, research lead me to the following solution:
router.post('/', function(req, res) {
var options = {
url: 'http://localhost:5984/_session',
method: 'POST',
json: {
"name": "admin",
"password": "password"
}
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log('authenticated');
}else{
console.log('not authenticated');
res.redirect('/')
}
});
});
When I do the same request via HttpRequester I get Statuscode 200 and {"ok":true,"name":null,"roles":["_admin"]} .. but via nodejs it won't do it even though it should be the same?!?
To validate user credentials against CouchDB just follow the example from CouchDB documentation.
curl -X POST http://localhost:5984/_session -d 'name=jan&password=apple'
After successful authentication you can keep CouchDB credentials the session storage.
I created a "proof of concept" code which is probably even not correct since i am not nodejs expert. But after some tuning it should work.
var http = require('http');
router.post('/login', function(req, res) {
var session = req.session;
request.post('http://localhost:5984/_session')
.auth(req.data.username, req.data.password, true)
.on('response', function(response) {
if(response.statusCode == 200) {
session.couchSession = req.data.username + ':' + req.data.password;
res.status(200);
res.send();
} else {
res.status(400);
res.send('Wrong credentials');
}
});
});
var search = 1 + req.url.indexOf('?'); throws an error saying the statement to my left is undefined. Im using passportjs to create a login/registration page on my angular frontend. trying to make a post request to nodejs results in the above error. Im entirely new to the mean stack and ive tried several different tutorials to get myself up and running but have had some road blocks. can someone point in the right direction?
I've played around with just about every file moving around code and trying different solutions but nothing works, or one problem is solved but another occurs.
server.js
// set up ========================
var DATABASE = "mongodb://localhost:27017/smartHomeDevices";
var express = require("express");
var mongoose = require("mongoose"); //require monogDB Driver
var morgan = require("morgan"); // log requests to the console (express4)
var bodyParser = require("body-parser"); // pull information from HTML POST (express4)
var methodOverride = require("method-override"); // simulate DELETE and PUT (express4)
var passport = require("passport");
//var _ = require("lodash");
var http = require('http');
//setup
//app.models =
require("./Models/moduleIndex");
// Bring in the Passport config after model is defined
require('./config/passport');
//registering routes
var routes = require("./routes");
//Create App
var app = express();
app.use(passport.initialize());
//Add Middleware for REST API
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json);
app.use(bodyParser.json({
type: 'application/vnd.api+json'
}));
app.use(methodOverride("X-HTTP-Method-Override"));
app.use(morgan("dev"));
//CORS Support, makes API Public
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,");
res.header("Access-Control-Allow-Headers", "Content-Type,Authorization");
next();
});
app.use("/", routes);
// Connect to the db
mongoose.connect(DATABASE);
mongoose.connection.once("open", function() {
var serv = http.createServer(function(req, res) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end();
console.log(routes(req.method, req.url));
}).listen(3000);
//module.exports = app;
console.log("Listening on 3000");
});
routes.js
//setup
var express = require('express');
var router = express.Router();
var jwt = require('express-jwt');
var auth = jwt({
secret: 'MY_SECRET',
userProperty: 'payload'
});
var ctrlProfile = require('./Controllers/ProfileController');
var ctrlAuth = require('./Controllers/RegisterUserController');
// profile
router.get('/profile', auth, ctrlProfile.profileRead);
// authentication
router.post('/register', ctrlAuth.register);
router.post('/login', ctrlAuth.login);
module.exports = router;
/*module.exports = {
"/smartDevices" : require("./Controllers/SmartDeviceController"),
"/registeredUsers": require("./Controllers/RegisterUserController")
};*/
resgisteredUsersControllers.js
//setup
//var Resource = require("resourcejs");
var restful = require("node-restful");
var passport = require('passport');
var mongoose = require('mongoose');
var User = mongoose.model('registeredUserModel');
var sendJSONresponse = function(res, status, content) {
res.status(status);
res.json(content);
};
module.exports.register = function(req,res) {
console.log(req);
console.log("nw logging res");
console.log(res);
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.username = req.body.username;
user.setPassword(req.body.password);
user.save(function(err) {
if(err)
console.log(err);
var token;
token = user.generateJwt();
res.status(200);
res.json({
"token" : token
});
});
next();
};
module.exports.login = function(req, res) {
passport.authenticate('local', function(err, user, info) {
var token;
// If Passport throws/catches an error
if (err) {
res.status(404).json(err);
return;
}
// If a user is found
if (user) {
token = user.generateJwt();
res.status(200);
res.json({
"token": token
});
} else {
// If user is not found
res.status(401).json(info);
}
})(req, res);
next();
};
/*module.exports = function(app, route) {
//setup controller for restful
// Resource(app,"",route,app.models.registeredUserModel).rest();
var rest = restful.model("registeredUserModel",
app.models.registeredUserModel
).methods(["get", "put", "post", "delete"]);
rest.register(app, route);
//return Middleware
return function(req, res, next) {
next();
};
};
*/
ProfileController.js
var mongoose = require('mongoose');
var User = mongoose.model('registeredUserModel');
module.exports.profileRead = function(req, res) {
// If no user ID exists in the JWT return a 401
if (!req.payload._id) {
res.status(401).json({
"message" : "UnauthorizedError: private profile"
});
} else {
// Otherwise continue
User
.findById(req.payload._id)
.exec(function(err, user) {
res.status(200).json(user);
});
}
};
Request object does not have url field.