How to authenticate both frontend and backend using OIDC (vuejs + nodejs)? - node.js

I am building a single page application with a Vuejs frontend and a Nodejs backend. Been reading tons about single sign on and oidc, and managed to implement authentication using Oidc for the frontend, where I get a token from my identity provider.
Not sure however, now, how to also implement this for the backend and where/when/how.
So currently, when a user accesses the page, in my router.js file, this happens:
router.beforeEach(vuexOidcCreateRouterMiddleware(store));
In the store then, I do this:
Vue.use(vuex);
const store = new Vuex.Store({
state: {
// holds current list of products
products: [],
},
getters,
mutations,
actions,
modules: {
// initialize PING-OIDC module
oidcStore: vuexOidcCreateStoreModule(
oidcSettings,
{ namespaced: false },
{
userLoaded: (oidcUser) => {
axios.defaults.headers.common.Authorization = `${oidcUser.token_type} ${oidcUser.access_token}`;
},
},
),
},
});
export default store;
So I set the authorization header, but now I am not sure where and how to proceed in the backend to also add + validate authentication there.
Very new to all this and there seem to be so many different way to proceed, so appreciate all the hints.
Currently, in server.js, I just do the following:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json())
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:5002',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
//get mysql db here
const data = require('./app/config/db.config.js');
const db = data.MySQL_DB;
// show all products
app.get('/api/productlist',(req, res) => {
const sql = "SELECT ID FROM Product_Table";
const query = db.query(sql, (err, results) => {
if(err) throw err;
console.log("productIds ", results);
res.send(JSON.stringify({"status": 200, "error": null, "response": results}));
});
});
// Create a Server
var server = app.listen(8080, function () {
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
So should I create a post request in there as well? Just not sure on how to validate the request there.. Thanks a lot for the help!

You need to clarify which data/routes/pages you want to protect.
You log in from your client -> send to the server (ex : api/login) -> respond to your client with credential -> store the user and credential
see passport.js for express
see accesscontrol for your data server side
Note that routes in client can be easily hack

Related

Why is my Heroku Express API data persistent even though it's only coming from a variable

I created a simple API using express, and deployed it to Heroku, this is the code for it:
const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
app.use(express.static("build"));
let notes = [
{
id: 1,
content: "HTML is easy",
date: "2022-05-30T17:30:31.098Z",
important: true,
},
{
id: 2,
content: "Browser can execute only Javascript",
date: "2022-05-30T18:39:34.091Z",
important: false,
},
{
id: 3,
content: "GET and POST are the most important methods of HTTP protocol",
date: "2022-05-30T19:20:14.298Z",
important: true,
},
];
const generateId = (arr) => {
const maxId = arr.length < 0 ? 0 : Math.max(...arr.map((item) => item.id));
return maxId + 1;
};
app.get("/", (req, res) => {
res.send(`<h1>Hello World!</h1>`);
});
app.get("/api/notes", (req, res) => {
res.json(notes);
});
app.get("/api/notes/:id", (req, res) => {
const id = Number(req.params.id);
const note = notes.find((note) => note.id === id);
if (note) {
res.json(note);
} else {
res.status(404).end();
}
});
app.delete("/api/notes/:id", (req, res) => {
const { id } = Number(req.params);
notes = notes.filter((note) => note.id !== id);
res.status(204).end();
});
app.post("/api/notes", (req, res) => {
const body = req.body;
if (!body.content) {
return res.status(400).json({
error: "Content Missing",
});
}
const note = {
content: body.content,
important: body.important || false,
date: new Date(),
id: generateId(notes),
};
notes = notes.concat(note);
res.json(note);
});
app.put("/api/notes/:id", (req, res) => {
const newNote = req.body;
notes = notes.map((note) => (note.id !== newNote.id ? note : newNote));
res.json(newNote);
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
as you can see, the data served to the frontend (A React app) comes from the '/api/notes' endpoint, this endpoint returns a response with the notes array.
After deploying to Heroku (https://fierce-chamber-07494.herokuapp.com/) the functionality of adding notes, and setting importance all work perfectly normal, but what I wasn't expecting was for the data to be persistent even after refreshing the page, visiting it in another device, etc. The data only comes from a variable, not a database, nothing. So why is it persistent? does Heroku modify the variable itself? how does this work?
The top-level code of an Express server often runs once, when you start up the server. Variables declared at that top level are then persistent if there are any handlers that reference them.
Consider how a client-side page with JavaScript works - the page loads, and then the JavaScript runs. If you keep the tab open for a couple hours and then come back to it, you'll see that variables declared on pageload still exist when you come back. The same sort of thing is happening here, except that the persistent environment is on your server, rather than on a client's page.
The code that starts up the Express server - that is, your
const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
...
and everything below it - doesn't run every time a request is made to the server. Rather, it runs once, when the server starts up, and then when requests are made, request handlers get called - such as the callback inside
app.get("/", (req, res) => {
res.send(`<h1>Hello World!</h1>`);
});
So, the variables declared at the top-level are persistent (even across different requests) because that server environment is persistent.
That said - something to keep in mind with Heroku is that with their free and cheap tiers, if no request is made to your app for a period of time (maybe 30 minutes), Heroku will essentially turn your server off by spinning down the dyno until another request is made, at which point they'll start your server up again, which will run the top-level code again. So while you'll sometimes see a top-level variable that appears to have its mutated values persist over multiple requests, that's not something to count on if your Heroku plan doesn't guarantee 100% uptime for your server.

How to act as a proxy server and modify incoming requests and then forwared them in NodeJS?

I am building a proxy service that will forward all but one kind of a POST request to another server.
I was planning on using express-http-proxy to do this but I can't find a way to modify the POST request on the fly.
For Example:
I want to catch all POST requests to /getdata and check if they have a field called username,
if they do I want to replace it with a custom username and then forward the request to another server and then forward the response from it back to the user.
Any help is appreciated. Any package or resource would help. Thanks.
I was facing a similar issue recently and ended up using http-proxy-middleware with the following config (based on this recipe):
const express = require('express');
const {createProxyMiddleware} = require('http-proxy-middleware');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
const options = {
target: '<your-target>',
changeOrigin: true,
onProxyReq: (proxyReq, req, res) => {
if (req.path === '/getdata' && req.body && req.body.userName) {
req.body.userName = "someOtherUser";
const bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
},
};
app.use(createProxyMiddleware(options));
app.listen(4001, () => console.log("listening ..."));
What did the trick for me was recalculating the Content-Length using this line:
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));

Can you call "express()" more than once in an ExpressJS application? (Or: what exactly is "express()" doing?)

I've been using Express for a while but suddenly I'm unsure about something pretty basic --
I'm trying to add custom middleware to a KeystoneJS application -- specifically I'm adding a JWT token endpoint to a TinyMCE custom field
The custom field is
export let Wysiwyg = {
type: 'Wysiwyg',
implementation: Text.implementation,
views: {
Controller: Text.views.Controller,
Field: importView('./views/Field'),
Filter: Text.views.Filter,
},
adapters: Text.adapters,
prepareMiddleware,
};
and prepareMiddleware is
function prepareMiddleware() {
const tinymcePath = dirname(require.resolve('tinymce'));
const app = express();
app.use(cors());
app.use('/tinymce-assets', express.static(tinymcePath));
app.post('/jwt', function (req, res) {
// NOTE: Before you proceed with the TOKEN, verify your users' session or access.
const payload = {
sub: '123', // Unique user id string
name: 'John Doe', // Full name of user
// Optional custom user root path
// 'https://claims.tiny.cloud/drive/root': '/johndoe',
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 60 minutes expiration
};
try {
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256'});
res.set('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify({
token: token
}));
} catch (e) {
res.status(500);
res.send(e.message);
}
});
return app;
}
This is all called from a KeystoneJS app that has its own ExpressJS server running. What exactly is the call to express() above doing? The ExpressJS API docs say
**express()**
Creates an Express application. The express() function is a top-level function exported by the express module.
var express = require('express')
var app = express()
I always understood this to be creating a new HTTP server. Surely you don't want to do that twice in a single app unless you're serving on different ports (which I'm not trying to do)?
Similarly, the KeystoneJS docs say
If you need to provide your own custom middleware for your system you
can create a custom App and include it in your exported apps.
class CustomApp {
prepareMiddleware({ keystone, dev, distDir }) {
const middleware = express();
// ...
return middleware;
}
}
Here, again, they're calling express().
What exactly happens when you callexpress()? It starts a new Express app, according to the docs, but I always thought this was equivalent to starting a new server. Surely, I thought, you can't start two servers on the same port.
Thanks for helping clear up my confusion -- I'm obviously not seeing the forest for the trees.
express() basically just creates a stack of middleware functions. It's not a server on its own.
Because it's just a stack of middleware, an Express app can be 'mounted' into another app. An example is shown here (edited for brevity):
var sub2 = express();
sub2.get("/", (req, res) => { res.json({}); });
var app = express();
app.use("/foo", sub2);
Defining and use()ing a new Express instance is really no different from loading any other middleware stack, such as express.Router().
As for binding to ports, usually, you'll only call the listen() helper function on the upper-most Express app instance. All this does is set up a basic HTTP server (so you don't have to) and registers your Express instance as the callback. It's little different from doing http.createServer(options, myUpperMostExpressApp);.

Share third-party API rate limit throughout session

I'm developing a simple app which consumes a third-party API (Shopify REST API), the Shopify API is throttled to allow unlimited requests over time though a leaky bucket algorithm which is detailed in their documentation.
I'm using the Shopify-api-node module to make my API calls, it supports an autoLimit option out of the box which automatically limits the rate in which requests are made so you never have to think about the API call limits.
This works for most Node.js scripts, but in you run into trouble in the context of an Express app where you need to share the API instance across different routes through a session because the autoLimit option is only reliable with one single Shopify instance.
Here's my code (notice that I'm creating a new Shopify instance for each route):
var express = require("express");
var session = require("express-session");
var Shopify = require("shopify-api-node");
var app = express();
var port = 3000;
app.use(session({
secret: "secret",
resave: false,
saveUninitialized: true
}));
app.get("/", (req, res) => {
var shopName = req.session.shopName = req.session.shopName || req.query.shopName;
var apiKey = req.session.apiKey = req.session.apiKey || req.query.apiKey;
var password = req.session.password = req.session.password || req.query.password;
var shopify = new Shopify({ shopName, apiKey, password, autoLimit: true });
shopify.on("callLimits", (limits) => console.log(limits));
var requests = Array.from({ length: 100 }, () => {
return shopify.shop.get()
});
Promise.all(requests)
.then((requestsRes) => {
return res.send(JSON.stringify(requestsRes));
})
.catch((err) => {
return res.status(500).send(err);
});
});
app.get("/other-route", (req, res) => {
var shopName = req.session.shopName = req.session.shopName || req.query.shopName;
var apiKey = req.session.apiKey = req.session.apiKey || req.query.apiKey;
var password = req.session.password = req.session.password || req.query.password;
var shopify = new Shopify({ shopName, apiKey, password, autoLimit: true });
shopify.on("callLimits", (limits) => console.log(limits));
var requests = Array.from({ length: 100 }, () => {
return shopify.product.list()
});
Promise.all(requests)
.then((requestsRes) => {
return res.send(JSON.stringify(requestsRes));
})
.catch((err) => {
return res.status(500).send(err);
});
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}!`)
});
Here's my package.json:
{
"name": "third-party-api-limit-issue",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.16.4",
"express-session": "^1.15.6",
"shopify-api-node": "^2.18.1"
}
}
When this script is run, go to this URL:
http://localhost:3000?shopName=liquify-app.myshopify.com&apiKey=9d1fae34bf670cc71230fee001486e82&password=dc6f650d5921eb40e7ab1e612e9dae7e
This'll make 100 requests to the Shopify API, store the API credentials in the session and display the shop JSON data.
This breaks if you open the same URL in two tabs at the same time, or open http://localhost:3000/other-route at the same time. This is happening because I can't figure out how to share the same Shopify API instance across my routes.
I need to be able to make as many calls to the API across my routes without running into the "Too many requests" error.
Any help would be greatly appreciated.
I think you have an impedance mismatch here. Shopify has a limit. It tells you exactly where you are at. So you make a request. The response header you get back tells you how many more requests you can make. That is the number you use.
Your approach of saying, my code will limit itself, so as not to tickle the monster over there, is going to bomb out as that is not the way to really architect the request-response cycle.
Maybe try looking at the response you get before issuing another request. If you have hit the limits, pause while you wait for the bucket to recharge a little. Standard stuff. Works well.

node expressjs websocket session passing

My code is listed below but I wanted to explain my thought process and have someone correct me at every point because I have been struggling to try and get this done the RIGHT way.
I've been struggling with this for some time(5days+) and I have not found a straight forward way to do this across the web.
So I have 2 separate node apps running. One running just express-js and another running a websocket server. I'm probably just a complete knucklehead with this, but here goes.
I setup a mongo session store. When a user successfully authenticates, the session gets created and I can re-direct the user to the logged in page. While the session lives, when the user hits the 'auth-page' I can just auto redirect the user to the 'logged in page'.
Now my understanding is, when the session gets created in the mongo-store, a cookie gets created on the web browser and it is this cookie that gets to the server for each request the page makes and express-js will nicely handle the magic internally for me and I can use something like
app.post('/', function (req, res) {
}
Where the req variable gets populated with the session id by express, because express got the cookie and decoded it.
This next part is where things are dicey and any suggestions in anyway will be a huge help.
What i'm wanting to do is, inside my app.post('/'...etc) is redirect to another page. This page loads a client which initiates a websocket connection to my websocket server and my websocket server is able to use this same session-id.
So here's the thing. My express-js http server runs as a separate process with its own port and my websocket server runs as a separate process with its own port as well. After doing enough research online, I found out many sources which indicated that, when my browser makes the connection to my websocket server it will send the cookie in the header somewhere to my websocket server. So in the browser, I have some javascript code that runs:
let clientSocket = new WebSocket("ws://socket.server.address:5005");
So then from my node websocket server, I can parse out the socket.upgradeReq.headers , get the cookie, and use that to get the session id and i'm in business. That describes what I've attempted to achieve below in my code. I have been successful doing this, however I've hit different issues when trying to parse the cookie.
Sometimes I get a single cookie & sometimes, I get multiple cookies taking the form
cookie_name1=cookie_value1;cookie_name2=cookie_value2;
cookie_name3=cookie_value3;cookie_name4=cookie_value4;
cookie_name5=cookie_value5;
Sometimes I get a single cookie & sometimes, I get multiple cookies taking the form
question 1 - why do I get multiple cookies being sent to my websocket server? Is that dictated strictly by the browser? What can I do about that if anything?
question 2 - Will the cookies ALWAYs come in that format? I would hate for the semicolon delimiter style to change and that break my code
question 3 - upon reviewing my code, my thought process can you suggest and guide me with a complete different/better implementation to achieve this? Can you suggest I change parts? My goal is to be able to spin up multiple different websocket servers & webservers and load-balance between them. I'm trying to find a reliable way to do this so that my code doesn't break... my node apps are just very frail, some direction would help. It seems like for nodejs, despite its maturity in 2017, good information lives only on stackoverflow,github issue threads and irc.freenode and I classify some of these things as basic...
packages and versions used
web-server package versions
---------------
express#4.15.2
express-session#1.15.2
mongodb#2.2.26
cookie-parser#1.4.3
body-parser#1.17.1
connect-mongodb-session#1.3.0
socket-server package versions
---------------
uws#0.14.1
below is my code
webserver.js
'use strict';
const bodyparser = require('body-parser');
const cookieparser = require('cookie-parser');
const express = require('express');
const app = express();
const express_session = require('express-session');
const connect_mongo = require('connect-mongodb-session')(express_session);
const port = process.env.NODE_WEBSERVER_PORT;
const _ = require('underscore');
const mongo_store = new connect_mongo({
uri: 'mongodb://mongo1.weave.local:27017/sessiondb',
collection: 'sess'
});
const session_time = 1000 * 60 * 5 ; // 5 minute(s)
app.use(express_session({
secret: 'superman',
cookie: {
maxAge: session_time,
httpOnly: false
},
store: mongo_store,
resave: false,
saveUninitialized: false,
name: 'inspect_the_deq',
httpOnly: false
}));
app.use(bodyparser.urlencoded({ extended: false }))
app.use(bodyparser.json());
app.set('view engine', 'pug');
app.set('views', __dirname+'/pugs')
app.use(express.static(__dirname + '/com/js'));
app.use(express.static(__dirname + '/com/asset'));
const mongo = require('mongodb').MongoClient;
const mongo_url = 'mongodb://mongo1.weave.local:27017/main';
let account = null;
let database = null;
mongo.connect(mongo_url, function(err, db) {
let collection = db.collection('account');
account = collection;
database = db;
});
app.get('/', function (req, res) {
if(req.session.user){
const user = req.session.user;
res.render('main', {message: 'user '+user+' logged in' });
console.log('session found logging you on');
}else{
res.render('login', {message: 'Login'});
console.log('no session exists');
}
});
app.post('/', function (req, res) {
const user = req.body.username, pass = req.body.password;
const seconds = session_time;
account.findOne({username: user, password: pass }, function(err, document) {
if( document !== null ){
req.session.user = user;
req.session.cookie.expires = new Date(Date.now() + seconds);
req.session.cookie.signed = true;
res.render('main', {message: 'user '+user+' logged in'});
console.log('some id is '+req.session.id);
console.log('cookie id is '+req.session.cookie);
console.log('sess id is '+req.sessionID);
}else
res.render('login', {message: 'Login', login_error: 'invalid username or password'});
});
});
app.listen(port, function () {
console.log('http server '+port);
});
Socket Server code here
'use strict';
const _ = require('underscore');
const uwsPlugin = require('uws').Server;
const socket_port = process.env.NODE_SOCKET_PORT;
const ws = new uwsPlugin({ port: socket_port, maxPayload: 0 });
//const Meepack = require('./core/meepack');
const cookieparser = require('cookie-parser');
const express_session = require('express-session');
const connect_mongo = require('connect-mongodb-session')(express_session);
const mongo_store = new connect_mongo({
uri: 'mongodb://mongo1.weave.local:27017/sessiondb',
collection: 'sess'
});
ws.on('connection', function connection(socket) {
'use strict';
console.log('client verification process ');
let headers = Object.keys(socket.upgradeReq.headers);
let upgradeReq = Object.keys(socket.upgradeReq.headers.cookie);
let cookie = socket.upgradeReq.headers.cookie;
//use the cookie here to get the session_id and do whatever you want
socket.on('close', function close(e) {
console.log('connection closed');
});
socket.on('message', function close(data) {
'use strict';
});
});

Resources