CSRF-protection for routes of proxied requests in express.js - node.js

I'm using http-proxy-middleware (and open to suggestions, but would like to stick to the modules which are proxying requests instead of creating new ones like request or http) to proxy requests to remote host.
Two problems I'm not seeing solutions to currenty:
1) I have a CSRF-protected form (via csurf). I would like it that the middleware checks the CSRF-token first, and in case it is valid only then proxies the request to another host, obtains the response and sends it to user. How to achieve such a setup?
2) http-proxy-middleware (and some other proxying modules) utilizes app.use to set one forwarding rule (append the route to the host), however I would like to have a more fine-grained control over routes - each of my routes must have its own endpoint on the remote host.
The code:
const express = require('express')
const csrf = require('csurf')
const cookieParser = require('cookie-parser')
const proxy = require('http-proxy-middleware')
var app = express()
var csrfProtection = csrf({ cookie: true })
app.use(cookieParser())
// not quite what I need, since different
// routes would utilize different endpoints
app.use('/api', proxy('http://example.com'))
app.get('/forms', (req, res) => {
res.send(
res.render('csrf-protected-forms.html', { csrfToken: req.csrfToken() })
)
})
app.post('/api/one', csrfProtection, (req, res) => {
response = // proxies to 'http://example.com/something/one'
// and obtains response
res.send(response)
})
app.post('/api/two', csrfProtection, (req, res) => {
response = // proxies to 'http://example.com/somethingelse/and/here/two'
// and obtains response
res.send(response)
})
app.listen(3000)

In your code csrf protection runs after proxied middleware. In case if you want protect only this two routes '/api/one','/api/two':
app.use(['/api/one','/api/two'], csrfProtection, proxy('http://example.com'))
app.use('/api', proxy('http://example.com'))
Or if you want protect all POST requests to API, you need somthing this:
app.use('/api', csrfProtection, proxy('http://example.com'))

Related

Node Express Cors issue

I cant figure why the cors express middleware wont work. cors, express, and ejs are all saved in package.json. The app works fine if I add corsanywhere proxy on the front end but id like to work around this on the server side. any help much appreciated I've been stuck on this.
the api is in the get View/index path
the error is:
Access to fetch at 'https://api.darksky.net/forecast/' 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.
const express = require('express');
const app = express();
const ejs = require('ejs');
const cors = require('cors');
const PORT = process.env.PORT || 3000;
// app.use((req, res, next) => {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'Origin', 'X-Requested-With')
// next();
// });
app.use(cors());
app.use(express.static(__dirname + '/Public'));
app.set('view engine', 'ejs');
app.get('/', cors(), (req, res) => {
res.render(__dirname + '/Views/index')
});
app.listen(PORT, () => {
console.log(`server is listening on ${PORT}`)
});
client side:
it works with the ${proxy} in there but id like to get rid of that
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
long = position.coords.longitude;
lat = position.coords.latitude;
var proxy = 'https://cors-anywhere.herokuapp.com/'
var api = `${proxy}https://api.darksky.net/forecast/042750f3abefefdfe2c9d43cf33ce576/${lat},${long}`;
fetch(api)
.then(response => {
return response.json();
})
.then(data => {
let {temperature, summary, icon,} = data.currently;
temperatureDegree.textContent = Math.floor(temperature);
temperatureDescription.textContent = summary;
locationTimezone.textContent = data.timezone;
setIcons(icon, document.querySelector('.icon'
w
``````
So, if you're trying to access some other service https://api.darksky.net/forecast/ (that you don't control) from your web page, then there is nothing you can do to make CORs work for that. It's up to the api.darksky.net server to decide if CORs is allowed or not. You can't change that.
You could make a request from your web page to your server to ask it to get some data from api.darksky.net for you and then return it back to your webpage (working as a simple proxy). Your server is not subject to any CORs limitations when accessing api.darksky.net. Only browsers are limited by CORs.
And, as you've found, you can also use a proxy service that enables CORs and fetches data for you.
Let's suppose you want to proxy the parts of the darksky API, you could do something simple like this:
const express = require('express');
const app = express();
const request = require('request');
const apiRouter = express.Router();
// maps /api/forecast/whatever to http://api.darksky.net/forecast/developerKey/whatever
// and pipes the response back
const apiKey = "yourAPIKeyHere";
apiRouter.get("/*", (req, res, next) => {
// parse out action and params
// from an incoming URL of /api/forecast/42.3601,-71.0589
// the /api will be the root of the router (so not in the URL here)
// "forecast" will be the action
// "42.3601,-71.0589" will be the params
let parts = req.path.slice(1).split("/"); // split into path segments, skipping leading /
let action = parts[0]; // take first path segment as the action
let params = parts.slice(1).join("/"); // take everything else for params
request({
uri: `https://api.darksky.net/${action}/${apiKey}/${params}`,
method: "get"
}).pipe(res);
});
app.use("/api", apiRouter);
app.listen(80);
Now, when you send this server, this request:
/api/forecast/42.3601,-71.0589
it will request:
https://api.darksky.net/forecast/yourAPIKeyHere/42.3601,-71.0589
and pipe the result back to the caller. I ran this test app and it worked for me. While I didn't see anything other than forecast URLs in the darksky.net API, it would work for anything of the format /api/someAction/someParams.
Note, you probably do NOT want to enable CORS on your server because you don't want other people's web pages to be able to use your proxy. And, since you're just sending requests to your own server now, you don't need CORS to be able to do that.

Protecting post routes NodeJS

I am working on a nodeJS application. So far i've learned you can protect routes with JWT and i've implemented this. The problem is, I am not sure how to protect a route where the user is allowed to post to.
Let's take a register route for example, this route will be used for user registration. I want users to be able to post to this, BUT only from my application. I dont want anyone to just be able to connect to it via postman and post whatever. How do you protect these kind of routes.
Thanks in advance.
You can use CORS middleware to allow only specific clients to access your server https://expressjs.com/en/resources/middleware/cors.html
Example:
var express = require('express')
var cors = require('cors')
var app = express()
var corsOptions = {
origin: 'http://example.com',
}
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})

Third party redirects to browser, not backend

Hello and thank you in advance.
We are running our Node.js/Express application in App Engine which serves a React front end as static files. At one point the user is able to redirect to a third-party so that the user can sign in and authorize some of their data which is managed by that third-party. We provide the redirect URI to the third-party as a parameter in the querystring, which is https://www.example.com/api/thirdparty/OAuthCallback.
When the user submits the authorization on the third-party side, they should be redirected to that URI, which is an API endpoint in our application where we process the result, and then in our backend we redirect the user to their dashboard. Instead, the user gets redirected to that URI in the browser.
We know (we think) we are doing the routing correctly because we are able to hit that end point from Postman and simulate the process that way.
The question is, why is this redirection going to the browser and not our backend?
Below you will find our server entry point (index.js) along with the route we expect to hit (thirdparty.js)
index.js:
//index.js
import express from "express";
import thirdparty from "./routes/thirdparty";
import httpsRedirect from "./middlewares/https-redirect";
const app = express();
var httpsPort = app.get('https-port');
app.use(httpsRedirect({httpsPort: httpsPort}));
app.set('trust proxy', true);
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(express.static(path.join(__dirname, '../build')));
app.use("/api/thirdparty", thirdparty);
app.get("/*", (req, res, next) => {
console.log('new request from: '+req);
res.sendFile(path.join(__dirname, '../build', 'index.html'));
});
app.listen(8080, ()=>console.log('Listening on port 8080'));
thirdparty.js (route):
// thirdparty.js
import express from "express";
const router = express.Router();
var oauth2 = require('./lib/OAuth2.0')();
router.get('/OAuthCallback', function (req, res) {
console.log("CODE--------------------------- : " + req.query.code);
var params = {
code: req.query.code,
redirect_uri: 'https://www.example.com/api/thirdparty/OAuthCallback'
}
oauth2.authCode.getOAuthToken(params, saveToken);
function saveToken(error, result) {
// do things with the result
res.redirect(process.env.HOST+"/dashboard");
}
});
export default router;

request header can't get cookie when using res.cookie of Express 4.x

i just want to try basic methods to set cookie and show in request head.
front-end is just a basic html form with username and password text input, use POST method to transfer data.
below is code based on express.
server just receive req.body, then set it as cookie with domain: localhost:1338/base
cookieRouter.js:
var express = require('express');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var router = express.Router();
router.use(bodyParser.urlencoded({ extended: false }));
router.use(cookieParser());
router.get('/', function (req, res) {
res.send('this is a router base page!');
});
router.get('/index1.html', function (req, res) {
res.sendFile(__dirname + '/index1.html');
});
router.post('/index1.html', function (req, res) {
res.cookie('name', req.body, { domain: 'localhost:1338', path: '/base' });
res.send(req.body);
});
module.exports = router;
app.js
var express = require('express');
var app = express();
var cookieRouter = require('./cookieRouter.js');
app.get('/', function (req, res) {
res.send('this is home page!');
});
app.use('/base', cookieRouter);
app.listen(1338);
after run app.js, request header has set-cookie value obviously. but can't get it into request header, and req.cookies is empty object {}, even after refreshing the web.
but if i just use simplest demo, it can work, for instance:
router.get('/', function (req, res) {
res.cookie('name', 'test');
});
one more thing, i feel the trouble with express is that only one res.send(), res.redirect()... can be sent as by default it will add head automatically, otherwise, it will come up with error: Can't set headers after they are sent.
someone said add return can solve this problem, but i failed, so want to how how to add, can anyone give an complete demo?
The cookie is missing because the domain attribute is incorrect -- 'localhost:1338' need to be changed to 'localhost'. Port information should not be included in domain.
Yes, according to the Network panel of browser dev tool, there is a Set-Cookie response header (as the screenshot displayed). However, if you check Application - Cookies panel in Chrome (or corresponding panel in other browsers), you will find that: the cookie specified by Set-Cookie header is not there. Browser does not store it and won't send it in the following HTTP requests.
Also, please note that as the cookie's path is /base, only the HTTP requests whose URL starts with /base can send the cookie.

Two apps in expressjs

I am building an app with express js which will have different clients like web and mobile. I didnt want to use one app for both as some middleware would be additional burden. For say like session middleware. So is it possible for one project to have two apps. And how would it work?
The app object that you make in express is a function(req,res,next) that is suitable for Express's own middleware chains. So you can use app.use to send requests matching a leading path fragment to an app defined elsewhere.
Docs: http://expressjs.com/api.html#app.use
$ npm install express
//mobile.js
var app = require('express')();
app.get('/', function(req, res){
res.send('Mobile Route')
});
module.exports = app;
//desktopApp.js
var http = require('http');
var express = require('express');
var desktopApp = express();
var mobileApp = require('./mobile.js');
desktopApp.use('/mobile', mobileApp)
desktopApp.use(desktopApp.router);
desktopApp.use(express.errorHandler());
desktopApp.get('/', function(req, res){
res.send('Desktop Route')
});
desktopApp.get('/mobile', function(req, res){
// Because Express respects the order that you set up the middleware chain,
// the mobileApp `/mobile` route gets first dibs to send a response or next()
res.send('Inaccessible Desktop Route')
});
desktopApp.get('/mobile/foobar', function(req, res){
// When mobileApp can't find any suitable route matching this path, it gives
// up, and desktopApp continues to pass the request down the middleware stack.
// It ends up matching this route, where we send a response
res.send('Desktop Route')
});
http.createServer(desktopApp).listen(3000, function(){
console.log('Listening on 3000');
});
// Results
$ curl localhost:3000/
Desktop Route
$ curl localhost:3000/mobile/
Mobile Route
See the vhost example on the express github repository.
You can have a "main" app, which routes the requests to one app or another. You should write a middleware to establish the conditions where one app or another are requested. express.vhost is a good example, but maybe you need other checks than the domain one.
main-app.js
(The file called to start the server.)
// load dependencies
var main = express();
main.use( express.vhost( 'mobile', require( './the-mobile-app' ) );
main.use( express.vhost( '*', require( './the-web-app' ) );
main.listen( /*...*/ )
the-mobile-app and the-web-app.js
var app = express();
//
// setup your application conf, middleware, and routes
//
module.exports = app;
I wanted to share a different approach that I used in a project recently:
function renderAppropriate(template1, template2){
return function(req, res){
if(req.session && req.session.mobileOn){
res.render(template1);
} else {
res.render(template2);
}
};
};
app.get('/', function(req, res, next){
// do some stuff
next()
}, renderAppropriate('someMobileTemplate', 'someDesktopTemplate')
);

Resources