How to make all express routers use validation code - node.js

When I create a router I need to add the following code in each module:
const express = require("express")
const cookieParser = require('cookie-parser')()
const cors = require('cors')({origin: true})
const router = express.Router()
const firebase = require("./firebase.js")
// https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint
// Must have header 'Authorization: Bearer <Firebase ID Token>'
const validateFirebaseIdToken = (req, res, next) => {
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!req.cookies.__session) {
res.status(403).send({ "error": 'Unauthorized'})
return
}
let idToken
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1]
} else {
// Read the ID Token from cookie.
idToken = req.cookies.__session
}
firebase.admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
req.user = decodedIdToken
return next()
}).catch(error => {
res.status(403).send({"error": 'Unauthorized'})
})
}
router.use(cors)
router.use(cookieParser)
router.use(validateFirebaseIdToken)
// api functions go here
module.exports = router
Initially I had this in a separate file and was sharing the router across modules, however, sharing caused problems. I don't want to copy and paste this code in each module file... How can I make sure that all routers are created like this with out having to copy and paste?

It's difficult to see exactly which part you want to share, but if you pull the validateFirebaseIdToken function into its own file, then you can import it like anything else. This is important because you can easily use it wherever you want. You may have routes which don't require authentication... in which case you wouldn't use this middleware.
validate-firebase-token.js
const firebase = require("./firebase.js")
const validateFirebaseIdToken = (req, res, next) => {
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!req.cookies.__session) {
res.status(403).send({ "error": 'Unauthorized'})
return
}
let idToken
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1]
} else {
// Read the ID Token from cookie.
idToken = req.cookies.__session
}
firebase.admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
req.user = decodedIdToken
return next()
}).catch(error => {
res.status(403).send({"error": 'Unauthorized'})
})
}
module.exports = validateFirebaseIdToken
... and now your router code looks like this:
const express = require("express")
const cookieParser = require('cookie-parser')()
const cors = require('cors')({origin: true})
const router = express.Router()
const validateFirebaseIdToken = require('./validate-firebase-token')
router.use(cors)
router.use(cookieParser)
router.use(validateFirebaseIdToken)
// api functions go here
module.exports = router

As stated by someone in the answers to my other question, you can apply middleware on to the app rather than a specific router. That way the middleware applies to all requests!

Related

How to private swagger document?

I'm using Swagger (on NodeJS, swagger-ui-express) to document my API.
This is how I build swagger server and setup my document on server
app.use('/apidocs', swaggerUI.serve, swaggerUI.setup(getSwaggerSpec()));
But now I need to private or set permission for someone in the team can access to document, and I expect all outside person can't access to my document
I tried with adding a middleware, but I didn't work.
app.use('/apidocs', (req, res, next) => {
if (req.query.userName === 'admin' && req.query.password === '12345678') {
console.log('-=-=-')
next();
} else {
res.send('Unauthenticated');
}
}, swaggerUI.serve, swaggerUI.setup(getSwaggerSpec()));
Code is fine. But when I access with exactly userName and password. It show a white page.
Why don't try to use an access token ?
const express = require('express');
const app = express();
const authToken = '123456';
app.use('/apidocs', (req, res) => {
const authHeader = req.headers.authorization || '';
const match = authHeader.match(/Bearer (.+)/);
const token = match[1];
if (token !== authToken) {
return res.status(401).send('Unauthorized');
} else {
return HERE YOUR RESPONSE
}
});
also you could to use express basics auth [https://github.com/LionC/express-basic-auth]
const basicAuth = require('express-basic-auth');
app.use("/api-docs",basicAuth({
users: {'yourUser': 'yourPassword'},
challenge: true,
}), swaggerUi.serve, swaggerUi.setup(swaggerDocument));

How to share token between different routes in node js?

In the user.js file, I created the token here with this code
if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
process.env.SECRET,
{
expiresIn: "50d", // >> on day
}
);
And the token work and everything is ok, But I want to use the token somewhere else, for example, here in cupon.js file
router.post("/cupon", async (req, res) => {
...
const token = req.header("authorization").substring(7);
...
I used this
const token = req.header("authorization").substring(7);
to get the token from the header, Is there a better way to get the token?
You can create a separate middleware for authentication and authorisation. It will help you to reuse it any where or in multiple routes, you can use the same auth middleware and if every thing goes good in auth, you can call next else send the response with 401 status code.
In Auth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, 'RANDOM_TOKEN_SECRET');
const userId = decodedToken.userId;
if (req.body.userId && req.body.userId !== userId) {
throw 'Invalid user ID';
} else {
next();
}
} catch {
res.status(401).json({
error: new Error('Invalid request!')
});
}
};
Import this Auth.js and pass it to your routes which needs it. This way you can unit test your auth layer and can re-use it anywhere in the code. The below code is for sample purpose:
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const stuffCtrl = require('../controllers/stuff');
router.get('/', auth, stuffCtrl.getAllStuff);
router.post('/', auth, stuffCtrl.createThing);
router.get('/:id', auth, stuffCtrl.getOneThing);
router.put('/:id', auth, stuffCtrl.modifyThing);
router.delete('/:id', auth, stuffCtrl.deleteThing);
module.exports = router;
For more details, you can check this link and follow along.

Jwt authorization/ passport

Hello I have a question about JWT, auth:
I created my auth class:
const passport = require('passport');
const Strategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
module.exports = (app) => {
const jwtConfig = app.config.jwt;
const Users = app.datasource.models.tb_users;
const options = {};
options.secretOrKey = jwtConfig.secret;
options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
const strategy = new Strategy(options,(payload, done) => {
Users
.findOne({where: payload.id})
.then(user => {
if(user){
return done(null,{
id: user.id,
login: user.login
});
}
return done(null,false);
})
.catch(error => done(error,null));
});
passport.use(strategy);
return {
initialize: () => passport.initialize(),
authenticate: () => passport.authenticate('jwt', jwtConfig.session)
};
}
this is my app.js:
const express = require('express');
const bodyParser = require('body-parser');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const authRouter = require('./routes/auth');
const authorization = require('./auth');
const config = require('./config/config');
const datasource = require('./config/datasource');
const Email = require('./utils/email');
const app = express();
const port = 3000;
app.set('port',port);
app.config = config;
app.email = new Email(app.config);
app.datasource = datasource(app);
console.log(app.config);
app.use(bodyParser.json({
limit: '5mb'
}));
const auth = authorization(app);
app.use(auth.initialize());
app.auth = auth;
indexRouter(app);
usersRouter(app);
authRouter(app);
module.exports = app;
And then I have my token login and validation method
app.route('/login')
.post(async (req,res)=>{
try {
const response = await usersControllers.signin(req.body);
const login = response.login;
console.log(login);
if(login.id && login.isValid){
const payload = {id: login.id};
res.json({
token: jwt.sign({data:payload}, app.config.jwt.secret,{expiresIn: '1h'})
});
}else{
console.log('entrou here');
res.sendStatus(HttpStatus.UNAUTHORIZED);
}
} catch (error) {
console.log('entrou here');
console.error(error.message);
res.sendStatus(HttpStatus.UNAUTHORIZED);
}
})
and in my method of searching all users I call this authorization:
app.route('/users')
.all(app.auth.authenticate())
.get((req,res)=>{
usersController
.getAll()
.then(data => {
res.json(data);
})
.catch(error=>{
console.log(error);
res.status(400);
});
})
here: .all(app.auth.authenticate())
At this point I start to get confused
in insomnia he if I go in the login route and then in the get all users route he gives unauthorized even though being authorized by the login route
I would like to know what I could do so I could use my get route with my token
Is this front end work?
Basically I would like to know how I would do to access other routes with my token other than insomnia
And what I could improve on my code is missing something to achieve this?
Not exactly clear what is being asked and you are referencing "insomnia" which is not represented in the code you have shared. You have asked about the token work on the frontend and will share some thoughts.
You need to save the token on the client when it is returned from POST to /login. As you have specified the that passport should attempt to extract the token from the request headers: options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); you will need to send the token within your headers when you are attempting to access secured routes. This is added as key/value to "Authorization" in the format "Bearer " + token.
For example using chaihttp test runner to post data to a secured route:
chai.request(server)
.post(`/books`)
.set('Authorization', `Bearer ${token.token}`) // set the auth header with the token
.send(data)
...

Route not being directed correctly

I have set up express to use the following paths:
const profile = require("./api/profile")
const events = require("./api/events")
app.use("/api/events", events)
app.use("/api/profile", profile)
Inside the events and profile index.js files I have the following:
const router = require('./../../modules/router.js')
router.get('/', (req, res) => {
})
module.exports = router
My router.js file:
const express = require("express")
const cookieParser = require('cookie-parser')()
const cors = require('cors')({origin: true})
const router = express.Router()
const firebase = require("./firebase.js")
// https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint
// Must have header 'Authorization: Bearer <Firebase ID Token>'
const validateFirebaseIdToken = (req, res, next) => {
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!req.cookies.__session) {
res.status(403).send({ "error": 'Unauthorized'})
return
}
let idToken
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1]
} else {
// Read the ID Token from cookie.
idToken = req.cookies.__session
}
firebase.admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
req.user = decodedIdToken
return next()
}).catch(error => {
res.status(403).send({"error": 'Unauthorized'})
})
}
router.use(cors)
router.use(cookieParser)
router.use(validateFirebaseIdToken)
module.exports = router
For some reason, the router mixes up the paths /api/events/ and /api/profile/ whenever I call them. For all other paths it works fine. How can I stop this from happening?
If you are using the same router for both events and profile, it could be the source of your issue.
Have you tested to create one router for each module?
Maybe try something like this for both events and profile:
const router = require('express').Router()
router.get('/', (req, res) => {
})
module.exports = router

How to protect routes in express.js?

For example, in Meteor, there's something like
Router.plugin('ensureSignedIn');
Router.plugin('ensureSignedIn', {
except: ['home', 'atSignIn', 'atSignUp', 'atForgotPassword']
});
So unsigned user cannot access other routes except above four.
How to do this in express.js? I'm using passport.js also.
I'm not familiar with Meteor, but you can do something like the following, assuming you want to make pages available to only authenticated users (passport).
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated())
return next();
else
// Return error content: res.jsonp(...) or redirect: res.redirect('/login')
}
app.get('/account', ensureAuthenticated, function(req, res) {
// Do something with user via req.user
});
The ensureAuthenticated function is just an example, you can define your own function. Calling next() continues the request chain.
I should use middleware for protect my routes, even to protect certain verbs in the same route:
for example: in my endpoint/route.js
// the require sentences are omitted
const express = require('express');
const { /*controllerFunctions*/ } = require('./controller');
const {routeGuard} = require('/*must create a route guard*/');
const router = express.Router();
router.route('')
.get(getAllResources)
;
router.route('/:id') //
.get(validateParam,getOneResource);
router.use(routeGuard);
router.route('/:id')
.post(validateParam,validateBody,postResource)
.patch(validateParam,validateBody,patchProblemById)
.delete(validateParam,deleteResource)
;
module.exports = router;
and my routeGuard file should be like this:
const { promisify } = require('util');
const jwt = require("jsonwebtoken");
const AppError = require('./appError');
const {User} = require('./../endpoints/users/model');
const wrapper = require('./try-wrapper');//try catch wrapper
module.exports.routeGuard = wrapper(async function (req, res, next){
// the err message is the same on purpose
const notAllowed = new AppError('Unauthorized: Invalid or Nonexistent credentials',401);
let token = null;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')){
token = req.headers.authorization.split(' ')[1];
}
if (!token) return next(notAllowed );
const payload = await promisify(jwt.verify)(token,process.env.KEY);
const user = await User.findById(payload.id);
if (!user) return next( notAllowed);
if ( ! user.hasSamePasswordSince(payload.iat) )return next( notAllowed );
req.user = user; // further use...
next();
});

Resources