I am trying to implement CSRF protection to my API endpoints,
I am using express and csurf, when making a post request using Axios from my react app I am receiving 403 invalid csrf token.
I have been searching all over for a solution but could not find one that fits.
For example, I am trying to send an Axios request to log out from the app (delete the session cookie) and receive the error.
A code that shows the error looks like that:
server.ts:
const router = express();
dotenv.config();
router.use(helmet...)
router.use(cookies());
router.use(express.static(path.resolve(__dirname, '../client/build')));
var production = process.env.NODE_ENV === 'production' ? true : false;
var csrfProtection = csurf({ cookie:{
httpOnly:production,
secure:production
} });
router.use(csrfProtection);
router.use(cors({ credentials: true, origin: true
,exposedHeaders:['itemsCount','notificationCount','courseCount'] }));
router.use((req, res, next) => {
res.cookie('XSRF-TOKEN', req.csrfToken(),{httpOnly:production,secure:production});
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, itemsCount');
if (req.method == 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
return res.status(200).json({});
}
next();
});
router.use('/api', userRoute);
const httpServer = http.createServer(router);
httpServer.listen(config.server.port, () =>
logging.info(NAMESPACE, `Server is running ${config.server.hostname}:${config.server.port}`));
user.ts:
router.post(`/${NAMESPACE}/logout`,verifyToken, logout);
userController.ts:
const logout = (req: Request, res: Response, next: NextFunction) => {
res.clearCookie('session');
return sendResponse(res, 200);
}
the axios request looks like that:
logout: () => axios.post(`/users/logout`, {},{withCredentials: true})
Both XSRF-TOKEN and _csrf cookies are secure and httpOnly.
I would much appreciate it if someone can point me to what I am doing wrong here.
Thanks.
Related
This is my node,js API,that works with no problems using postman, but when I try to make a request from a different origin like a react project the request is blocked
const express = require('express');
const mongoose = require('mongoose');
const app = express();
const port = process.env.PORT || 9000;
const routes = require('./routes/routes');
const token = require('./config/config');
const cors = require('cors')
app.use(cors())
app.use(express.json());
app.use('/api', routes);
app.listen(port, () => console.log('server listening on port', port));
const url = "mongodb://localhost/titles_db";
mongoose.connect(url,{})
.then( () => console.log('DB connected'))
.catch( (e) => console.log('Erorr on db connection'));
and this is the function that is called on my request
searchTitles = (req, res) => {
const terms = req.query.terms;
const format = req.query.format;
titleSchema.find({title: {$regex:terms, $options: 'i'}})
.then( data => {
if(format == 'json')
res.json(data);
else{
res.setHeader("Content-Type", "text/plain");
res.send(data);
}
})
.catch( error => res.json( {message: error}))
}
and here is the function that makes the request on the frontend
const getFieldText = e => {
setTerm({term: e.target.value });
const url = `http://localhost:9000/api/titles/?terms=${e.target.value}&format=json`
fetch(url)
.then(response => console.log(response))
.then(data => console.log(data));
}
even including cors library on node
const cors = require('cors')
app.use(cors())
I get this response
Response { type: "cors", url: "http://localhost:9000/api/titles/?terms=aaaaaa&format=json", redirected: false, status: 403, ok: false, statusText: "Forbidden", headers: Headers, body: ReadableStream, bodyUsed: false }
I added an options array but I have the same result
var corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
app.use(cors(corsOptions))
configure the cross headers like this (in your server node config):
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', "http://localhost:8080");
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, authorization, Access-Control-Allow-Origin');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', 'true');
// Pass to next layer of middleware
next();
});
I am calling a local API from ionic project testing on android but it shows Http response status code 0 unknown error I did look up for many solutions but non helped.
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
res.header("Access-Control-Allow-Credentials", "true",);
res.header('Access-Control-Allow-Headers', 'Origin, X-Auth-Token, Accept, Content-Type, Authorization, Content-Length, X-Requested-With, x-access-token, Access-Control-Request-Method, Access-Control-Request-Headers');
next();
});
method requesting API.
async loginWithFacebook(data) {
console.log("api USER data from facebook ");
console.log(data);
let headers = new HttpHeaders({
'Content-Type': 'application/json'
});
let options = {
headers: headers
};
return await (this.http.post(environment.endpoint + "/users/facebook-login", data, options)).toPromise();
}
it's unable to call the API for some reason.
You are just assigning header to the HTTP response, which will not help you with the CORS of the request. Doing the following might help.
Install cors package in the server root using terminal.
npm i cors
Import cors to your app.js (the root file of your server).
const cors = require('cors')
Use cors for your App.
app.use(cors())
Your final code should look something like this.
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
Source
I'm trying to set up a basic user signup form with React, Node, and Express, and using fetch. However, I'm getting the following errors in the Chrome console when I try and send a post request:
1) "OPTIONS http://localhost:7001/v1/register 500 (Internal Server Error)"
2) "Access to fetch at 'http://localhost:7001/v1/register' from origin 'http://localhost:3001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status."
My eventual goal is to save the user's email and password in a database, but for now all I want is for the request to go through to the backend and have the backend log the body to make sure everything works. I've tried several different ways of setting headers, and I have no idea what's wrong. Below is the code.
Frontend form submit function:
handleSubmit(e) {
e.preventDefault();
const signUpInfo = this.state; // { email: 'test#gmail.com', password: '123' }
console.log(signUpInfo);
fetch('http://localhost:7001/v1/register', {
method: 'POST',
body: JSON.stringify(signUpInfo),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(response => console.log('Success:', response))
.catch(error => console.error('Error:', error));
}
server.js
const express = require('express');
const compression = require('compression');
const cfg = require('config');
const path = require('path');
const logger = require('morgan');
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser');
const config = require('config');
const app = express();
app.use(compression());
app.use(bodyParser());
app.use(cookieParser());
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.static(path.join(__dirname, 'public')));
app.use(function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,DELETE");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Access-Control-Allow-Headers, Content-Type, Authorization, Origin, Accept");
res.setHeader('Access-Control-Allow-Credentials', true)
next();
});
// CONTROLLERS
const userController = require('./controllers/userController.js');
// ROUTES
app.post('/v1/register', userController.register);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
app.listen('7001', function() {
console.log('API server listening on port 7001!');
});
module.exports = app;
userController.js
exports.register = async (req, res, next) => {
try {
console.log(req.body);
res.status(200).json({ status: 200, data: req.body, message: "test" });
} catch (err) {
console.log(err);
res.status(500).json({ status: 500, data: null, message: err });
}
}
All I'm looking for is for the backend console to print out the body. It works with axios and $.ajax, but not with fetch. I've also tried using a proxy server to no avail (and would like to get it to work without a proxy).
Not sure if this is relevant, but I'm using Chrome as the browser and Sequelize.
Any help would be greatly appreciated. I feel like I'm missing something fundamental. Any helpful articles to deepen my learning would be a plus!
Instead of using
const app= express();
try to use
const app=express().use('*', cors());
and remove
app.use(function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,DELETE");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Access-Control-Allow-Headers, Content-Type, Authorization, Origin, Accept");
res.setHeader('Access-Control-Allow-Credentials', true)
next();
});
see if this works.
First Install "cors":
npm i cors
Second import "cors":
cors = reqquire("cors");
Third use "cors":
const app = express();
app.use("*", cors());
I am getting some strange issue in my angular 6 and nodeJS web app.
I have to implement csrf protection so I have implemented csurf in node js, my node js code is given below,
let express=require('express');
let app=express();
var cookieParser = require('cookie-parser')
csrf = require('csurf');
//import body parser
let bodyParser=require('body-parser');
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin","*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,X-XSRF-Token");
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header('Access-Control-Allow-Credentials', true);
next();
});
app.use(cookieParser('ksdnfjn9834f80sa9asjfasdj'));
app.use(csrf({ cookie: true }));
app.use(function(req, res, next) {
console.log(req.csrfToken());
res.cookie('XSRF-TOKEN', req.csrfToken(),{ httpOnly: false,Path:"/" });
return next();
});
app.use('/api',apiRoutes);
app.use('/admin',adminapiRoutes);
app.listen(3000,function(){
console.log('api is listening on port'+3000);
})
my angular js code looks like this, to call api post request,
this.http.post(this.contact_api_url, {
"name": this.contact_name,
"email": this.contact_email,
"message": this.contact_message
},{withCredentials: true,headers: new HttpHeaders().set('X-XSRF-TOKEN', "token_get_in_cookie" ) }).subscribe((val) => {
this.renderer.removeClass(loda_element,'show');
//var msg=JSON.stringify(val);
alertify.success(val['message']);
this.contact_name = '';
this.contact_email = '';
this.contact_message = '';
grecaptcha.reset();
this.captcha_response = '';
}, error => {
this.renderer.removeClass(loda_element,'show');
alertify.error("Error Occurred. Try Again.");
});
Now the issue is it is not setting header any way, even not adding any other header I have also added below code in imports
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
}),
but nothing works it is not adding X-XSRF-TOKEN header, I have also implemented interceptor as below,
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestToForward = req;
let token = this.tokenExtractor.getToken() as string;
const headers: any = {};
if (token !== null) {
headers['X-XSRF-TOKEN'] = token; // add it to the header
requestToForward = req.clone({ setHeaders: headers });
}
return next.handle(requestToForward);
}
I am also getting token value which is set by node js code in XSRF-TOKEN but header is not setting.
Can any one have idea why header is not setting in angular 6?
Note: I am working on localhost so nodeJS is running on http://localhost:3000 and angular is running on http://localhost:4200
I have a REST Api, and all endpoints must send a response when the user has an authentication token (I use the jwt token).
everything works fine when I test my code using postman, but from front not working(session closes after OPTION request, and on the request header bearer token not set).
Authentication Middleware
module.exports = function(req, res, next) {
const authorization = req.headers['authorization'];
console.log(authorization);
const token = authorization
? authorization.replace('Bearer ', '')
: null;
if (!token)
return res.status(403).send({ auth: false, message: 'No token provided.' });
jwt.verify(token, config.secret, function(err, decoded) {
if (err)
return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' });
req.userId = decoded.id;
next();
});
}
route
const Router = require('express').Router;
//Authentication Middleware
const requireAuthentication = require('../middlewares/').Auth()
module.exports = () => {
let router = new Router();
router.use(requireAuthentication);
router.use('/accounts', require('./account')());
router.use('/projects', require('./projects')());
return router;
};
with authentication
https://i.stack.imgur.com/cAFw5.png
without authentication
https://i.stack.imgur.com/VUuuv.png
The reason was in access headers
I add middleware in bootstrap file.
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
if ('OPTIONS' === req.method) {
res.send(200);
}
else {
next();
}
});
Try to use Express Cors: https://github.com/expressjs/cors
Simple Usage (Enable All CORS Requests)
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell a browser to let a web application running at one origin (domain) have permission to access selected resources from a server at a different origin. A web application makes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, and port) than its own origin.
Read more about CORS here https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS