I'm setting up a universal React app and using this project as a base. I am successfully proxying requests (using http-proxy) to my Laravel backend. However, I'm new to Nodejs and I don't know how the best method to securely store a JWT from the proxied server to the client.
My initial thought was to store the token to localStorage, but the problem is that the express server won't have access to it. So my next guess would be to store it as a cookie, but I'm not sure how to store it on the client or include it as a header for all outgoing requests (additionally, I would likely need some sort of csrf middleware).
So how would I manipulate the response from my api server to put a token in a cookie that is set in the client, then use that as a bearer token for all api requests?
// server.js
const targetUrl = 'http://' + config.apiHost + ':' + config.apiPort;
const app = new Express();
const server = new http.Server(app);
const proxy = httpProxy.createProxyServer({
target: targetUrl,
changeOrigin: true
});
// Proxy to Auth endpoint
app.use('/auth', (req, res) => {
// on a successful login, i want to store the token as a cookie
proxy.web(req, res, {target: targetUrl});
});
// Proxy to api endpoint
app.use('/api', (req, res) => {
// use the token in the cookie, and add it as a authorization header in the response
proxy.web(req, res, {target: targetUrl});
});
Given that the response from the auth endpoint in laravel is like this:
{
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
}
This code will do what you want:
// server.js
const targetUrl = 'http://' + config.apiHost + ':' + config.apiPort;
const Express = require('express');
const http = require('http');
const httpProxy = require('http-proxy');
const app = new Express();
const server = new http.Server(app);
const Cookies = require( "cookies" )
const proxy = httpProxy.createProxyServer({
target: targetUrl,
changeOrigin: true
});
// Proxy to Auth endpoint
app.use('/auth', (req, res) => {
// on a successful login, i want to store the token as a cookie
// this is done in the proxyRes
proxy.web(req, res, {target: targetUrl});
});
// Proxy to api endpoint
app.use('/api', (req, res) => {
// use the token in the cookie, and add it as a authorization header in the response
var cookies = new Cookies( req, res )
req.headers.authorization = "JWT " + cookies.get('jwt-token');
proxy.web(req, res, {target: targetUrl});
});
proxy.on('proxyRes', function(proxyRes, req, res) {
if (req.originalUrl === '/auth') {
var cookies = new Cookies( req, res )
var body = '';
var _write = res.write;
var _end = res.end;
var _writeHead = res.writeHead;
var sendHeader = false;
res.writeHead = function () {
if (sendHeader) {
_writeHead.apply( this, arguments );
}
}
res.write = function (data) {
body += data;
}
res.end = function () {
sendHeader = true;
var parsed = JSON.parse(body);
cookies.set('jwt-token', parsed.token);
_write.apply(this, [ body ]);
_end.apply(this, arguments);
}
}
});
Related
I am creating a project in DialogFlow and NodeJS where I want to call my fulfillments with a webhook.
In my NodeJS server, I have multiple routes for different functions/intents. For example, /getWeather calls a weather API to return a response about the weather in a specific city. Or /getMovie calls an API to return information about a movie.
DialogFlow only allows for one webhook API, so my question is, how can I call a generic API "/" where it can handle all the different routes and call the correct route when it needs to?
I can use the inline editor on DialogFlow to call each API with the correct route; however, I want to use a single webhook rather than using the firebase functions to call the correct intents.
I can't seem to find example of this online where multiple routes are handled with a generic route.
Image of my Code Stack
index.js:
const http = require('http');
const app = require('./app');
const port = process.env.PORT || 3000;
const server = http.createServer(app);
server.listen(port);
server.post
app.js
const express = require('express');
const app = express();
const morgan = require('morgan');
const bodyParser = require('body-parser');
const mongoose= require('mongoose');
const issuesRoutes = require('./API/Routes/issues');
const movieRoute = require('./API/Routes/getmovie');
const resolvedtaskroute = require('./API/Routes/resolvedtask');
const newtaskRoute = require('./API/Routes/newtask');
mongoose.connect('link', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err));
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use((req, res, next) => {
res.header('Acces-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS'){
res.header('Access-Control-Allow-Methods', 'PUT, POST, PATCH, DELETE, GET');
return res.status(200).json({});
}
next();
});
//routes to handle requests
app.use('/issues', issuesRoutes);
app.use('/newtask', newtaskRoute);
app.use('/resolvedtask', resolvedtaskroute);
app.use('/getmovie', movieRoute);
//error handling
app.use((req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
})
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.json({
error: {
message: error.message
}
})
})
module.exports = app;
Example of one of my routes: getMovie.js
const express = require('express');
const router = express.Router();
const http = require('http');
router.post('/', (req, res, next) => {
const movieToSearch = req.body.queryResult.parameters.movie;
const API_KEY = 'XXXXX';
const reqUrl = `http://www.omdbapi.com/?t=${movieToSearch}&apikey=${API_KEY}`
http.get(
reqUrl,
responseFromAPI => {
let completeResponse = ''
responseFromAPI.on('data', chunk => {
completeResponse += chunk
})
responseFromAPI.on('end', () => {
const movie = JSON.parse(completeResponse)
let dataToSend = movieToSearch
dataToSend = `${movie.Title} was released in the year ${movie.Year}. It is directed by ${
movie.Director
} and stars ${movie.Actors}.
}`
return res.json({
fulfillmentText: dataToSend,
source: 'getmovie'
})
})
},
error => {
return res.json({
fulfillmentText: 'Could not get results at this time',
source: 'getmovie'
})
}
)
})
module.exports = router;
It is very clear that Dialogflow allows one webhook POST url where every call for intents are made. IF you want to use different API services inside then You should define a webhook and inside the webhook just call the functions which are related to intents using intentMAP. On each function call the external API and return the response back to dialogflow. I will describe a bit more about it using dialogflow-fulfillment.
first thing you need is a webhook POST route for handling dialogflow requests and responses and inside it you need to map intents to its specific function as like:
const { WebhookClient } = require("dialogflow-fulfillment");
const movieService= require("your function for movie API");
router.post("/", async (req, res, next) => {
const agent = new WebhookClient({ request: req, response: res });
const movie = new movieService(agent);
let intentMap = new Map();
intentMap.set("Movie Intent", () => {
//make an api call inside this function
return movie.getinfo();
});
if (agent.intent) {
agent.handleRequest(intentMap);
}
});
Now create another file for external API calls which will be like
async getMovie(){
// get all required paramters from dialogflow here and call APIS and return back response using
agent.add("The info about movie is");
}
I'm trying to set a cookie with a post method in order to do some db query and put it back in the cookie value, as well as returning a json with the user data.
It works, the cookie is set and I get the json on http://localhost:8080
but I get a message from the compiler:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
How can I fix it so it won’t make this error?
my file structure is:
root/ app.js
root/controllers/ cookie.controller.js
root/routes/ cookie.route.js
app.js
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const app = express();
const port = process.env.PORT || process.argv[2] || 8080;
app.use(cookieParser());
app.use(require('./routes/cookies'));
app.use(cors());
app.listen(port, () => console.log('cookie-parser demo is up on port: ' + port));
cookie.route.js
const express = require('express');
const cookieController = require('../controllers/cookies');
const router = express.Router();
router.use(require('cookie-parser')());
router.post('/', router.use(cookieController.getCookie));
module.exports = router;
cookie.controller.js
exports.getCookie = (req, res, next) => {
let auth = req.cookies.auth;
//...db queries, get userData
let userData = {
id: '123',
token: 'sfsdfs34',
email: 'user#gmail.com'
};
// if cookie doesn't exist, create it
if (!auth) {
res.status(200)
.cookie('auth', userData.id)
.json({ message: 'it works!', user: userData });
req.cookies.auth = userData.id;
}
next();
};
You're modifying the request cookie headers after sending the response at the end of your getCookie controller. You should remove req.cookies.auth = userData.id, and use res.cookie() instead before sending the response.
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
app.get('/', (req, res) => {
if (!req.cookies.auth) {
res.cookie('auth', { id: '123' })
}
res.json({ message: 'It worked!' })
})
app.listen(8080, () => console.log('http://localhost:8080))
Problem was solved after deleting the cors from app.js
I would use sockets in a separate route file .
I'm using the method mentioned in this answer : Express 4 Routes Using Socket.io
I have copied exactly the same logic. In server file :
var http = require("http");
var admin = require('firebase-admin');
var firebase = require("firebase");
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var port = process.env.app_port || 8080; // set our port
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var server = app.listen(port);
var io = require("socket.io")(server);
var routerProj = require("./routes/routes")(io);
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT ,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,*");
next();
});
var config = {
.... DB Configuration ....
};
firebase.initializeApp(config);
var serviceAccount = require("./ServiceAcountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://datatable-18f93.firebaseio.com"
});
io.on("connection", function (client) {
console.log("Un client est connecté !");
//routerProj(client);
});
app.use("/v1", routerProj, function (req) {
//Create HTTP server and listen on port 8000 for requests
});
My connection socket is working and the console.log runs in terminal
routes.js file
var express = require("express"); // call express
var router = express.Router(); // get an instance of the express Router
var admin = require("firebase-admin");
var returnRouter = function (client) {
router.use(function (req, res, next) {
// do logging
client.on('save-message', function (socket) { console.log("heheyy") })
});
router
.route("/")
.get(function (req, res, err) {
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("/");
// Attach an asynchronous callback to read the data at our posts reference
ref.once("value", function (snapshot) {
var list = [];
snapshot.forEach(function (elem) {
list.push(elem.val());
})
list = JSON.stringify(list);
//list = JSON.parse(list)
console.log(err);
//console.log(JSON.stringify(list))
res.send(list);
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
res.status(500).send(errorObject.code);
});
});
router
.route("/")
.post(function (req, res, err) {
console.log(req.body);
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("/");
ref.push(
{
"text": req.body.text
}
);
});
return router;
}
module.exports = returnRouter;
save-message is emit in Angular when my arr is running :
ngOnInit() {
this.socket.emit('save-message', { room: "hello" });
}
Save-message event is not getting read neither the routes file, In my angular application services does not get data from routes. and console.log in get and post routes does not work.
My question is how to get sockets working in a reparate file ?
You should move the socket.io listener outside of the express use route. It's not really clear why you would want it there as it will register a new listener every time someone makes a request to your v1 endpoint.
You likely aren't seeing the messages because the listener does not register until someone makes a request to the v1 endpoint and the client already sent its message.
var returnRouter = function (client) {
// do logging
client.on('save-message', function (socket) {
console.log("heheyy");
});
...
};
I am working with a jwt token to validate user registration. A unique url is sent to user's email and it redirects to the authentication page, the token is decoded on server side and I need to send this json data to angular, on client side. How can I do this using token as query parameter and retrieve it using ngResource?
server.js
'use strict';
var express = require('express');
var app = express();
var router = express.Router();
var bodyParser = require('body-parser');
var nodemailer = require('nodemailer');
var jwt = require('jsonwebtoken');
var moment = require('moment');
var port = process.env.PORT || 5000;
app.use(express.static('./src/client/'));
app.use(express.static('./'));
app.use(express.static('./.tmp'));
app.use('/*', express.static('./src/client/index.html'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// sign with default (HMAC SHA256)
var expires = moment().add(12, 'hours').valueOf();
var token = jwt.sign({
user: 'userdata',
iat: Math.floor(Date.now() / 1000),
expireIn: expires
}, 'thisismysecretstring');
// nodemailer sendMail function
app.post('/sendMail', function(req, res) {
var transporter = nodemailer.createTransport('smtp://b204bf8f6ede15:71b5c1473852e2#mailtrap.io:2525');
var data = req.body;
var mailOptions = {
from: 'noreply#ixfire.com',
to: data.email,
subject: 'Email sent by ' + data.displayName,
html: '<p>Please click on url below to register</p><br>CLICK HERE'
};
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
return console.log(error);
}
console.log('Message sent: ' + info.response);
});
res.json(token);
});
// decode token from url parameter
app.get('/auth', function(req, res) {
var token = req.query.token;
try {
var decoded = jwt.verify(token, 'thisismysecretstring');
if (decoded.exp <= Date.now()) {
res.end('Access token has expired', 400);
}
res.json(decoded);
} catch (err) {
console.log(err);
res.json(err);
}
});
// router.use(function(req, res, next) {
// var token = req.query.token;
// try {
// var decoded = jwt.verify(token, 'thisismysecretstring');
// res.json(decoded);
// } catch (err) {
// console.log(err);
// res.json(err);
// }
// });
// app.use('/auth', router);
app.listen(port, function () {
console.log('Express app listening on port: ' + port);
console.log(__dirname);
});
token.js
(function() {
'use strict';
angular
.module('xfire.token', ['ngResource'])
.factory('Token', function($resource) {
return $resource('auth/:token', {
token: '#token'
});
});
})();
url format:
http://localhost:3000/auth/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZ2NmYWJyaSIsImlhdCI6MTQ2ODI0NDI1NCwiZXhwaXJlSW4iOjIxNjAwMDAwfQ.5rs1rlWMTTcap4idG-XOU-UiwbU0YzlnAYjm9Vwz-B0
I usually send it in a header, most often I name it x-auth-header.
I don't however use nor I recommend anyone (except for playing around) to use ngResource, as it is limiting.
Personally, I use restangular, with request/response interceptors.
Response interceptor to grab and decode the token, and request interceptor to authorize the request with "Bearer" + tokenString.
I am trying to implement the simplest example:
var http = require('http'),
var httpProxy = require('http-proxy');
httpProxy.createServer(function (req, res, proxy) {
//
// I would add logging here
//
proxy.proxyRequest(req, res, { host: 'www.google.com', port: 80 });
}).listen(18000);
When I configure my browser to use this proxy and I navigate to www.google.com I receive no response. What is that I am doing wrong?
I'm using Windows 7 Chrome
Here is an simple example how to log requests.
I use a similar to log all my domains to one database.
I copied much from http://blog.nodejitsu.com/http-proxy-middlewares (archived)
var fs = require('fs'),
http = require('http'),
httpProxy = require('http-proxy'),
logger = function() {
// This will only run once
var logFile = fs.createWriteStream('./requests.log');
return function (request, response, next) {
// This will run on each request.
logFile.write(JSON.stringify(request.headers, true, 2));
next();
}
}
httpProxy.createServer(
logger(), // <-- Here is all the magic
{
hostnameOnly: true,
router: {
'example1.com': '127.0.0.1:8001', // server on localhost:8001
'example2.com': '127.0.0.1:8002' // server 2 on localhost:8002
}
}).listen(8000);
I am not sure if this helps because the posted information are really short.
But I found a post that they updated the api ...
you might want to check out this post:
Updating to node-http-proxy v0.5.0
http://blog.nodejitsu.com/updating-node-http-proxy
I done like logging the request headers object on the event proxyReq
const http = require('http'),
httpProxy = require('http-proxy'),
fs = require('fs');
const proxy = httpProxy.createProxyServer({});
const logFile = fs.createWriteStream('./requests.log');
proxy.on('proxyReq', function (proxyReq, req, res, options) {
//Log incoming request headers
logFile.write(JSON.stringify(req.headers, true, 2));
});
const server = http.createServer(function (req, res) {
proxy.web(req, res, {
changeOrigin: true,
target: 'example1.com'
});
});
console.log("listening on port 5050")
server.listen(5050);