How to use csurf within a custom middleware? - node.js

I have managed to get csurf working in my express app as a regular middleware. However, I'd like to add it to my custom authentication middleware to both avoid having to include csurf in every route and also to avoid forgetting to use it. How should I call csurf within a custom middleware?
For example, say I have this middleware using express-session to limit access to logged-in users:
export const auth = async (req, res, next) => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
next();
} catch {
throw new PrivateRouteError();
}
};
This answer has a way of doing this but I was unable to implement it. Here's what I tried:
export const auth = async (req, res, next) => {
const csrf = csurf({ cookie: true, ignoreMethods: [] });
csrf(req, res, async () => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
next();
} catch {
throw new PrivateRouteError();
}
});
};
However, the result is that csurf does not block access for a missing CSRF token and the PrivateRouteError is not caught and crashed the app (if the user has not authenticated, if they are it works fine).
Is there a neat way to bundle csurf into my middleware or should I just manually add it to all the routes that use the auth middleware?

Okay, I was clearly overthinking this last night. It's enough to get rid of the next() call and put csurf stuff after the catch block.
export const auth = async (req, res, next) => {
const { uid } = req.session;
try {
const user = await User.query().findById(uid);
req.session.role = user.role;
} catch {
throw new PrivateRouteError();
}
const csrf = csurf({ cookie: true, ignoreMethods: [] });
csrf(req, res, next);
};

Related

JWT verification for Swagger NodeJs API middleware

I generated with Swagger a Node js API. I'm trying to add a jwt token check in my API to allow access to protected ressources. I would like to use something similar to passport js but I don't understand where I need to add the "passport.authenticate" method.
This is the way the app is initialized:
function initializeApp(swaggerOption, swaggerDoc) {
const p = new Promise((resolve, reject) => {
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerOption));
// Serve the Swagger documents and Swagger UI
app.use(middleware.swaggerUi());
resolve(app);
})
});
return p;
}
exports.initializeApp = initializeApp;
initializeApp(options, swaggerDoc ).then((app) => {
// Start the server
http.createServer(app).listen(serverPort, function () {
console.log('Your server is listening on port %d (http://localhost:%d)', serverPort, serverPort);
console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort);
});
})
And an example of a controller
'use strict';
var utils = require('../utils/writer');
var Auth = require('../service/AuthService');
module.exports.authenticatePUT = function authenticatePUT (req, res, next) {
var body = req.swagger.params['body'].value;
Auth.authenticatePUT(body)
.then(function (response) {
utils.writeJson(res, response);
})
.catch(function (response) {
utils.writeJson(res, response);
});
}
Since there is no route like so I can siply follow passport js's syntax
app.get ('/ profile',
passport.authenticate ('bearer', {session: false}),
function (req, res) {
res.json (req.user);
});
and no documentation (or I don't find) for middleware methods I don't find where I can add the jw token verification. If someone has some examples or explanations I would be verry happy :)
Thanks !
I am not using passport, but a simple example is like so:
In your routes the middleware is defined just as a function to run before proceeding further. It will implicitly receive the request data.
app.post(
'/api/object/create',
auth.getToken, // << middleware evaluate
objectController.create
)
I have created my own middleware evaluation, it looks like this:
getToken: function (req, res, next) {
const bearerHeader = req.headers['authorization']
// Get the bearer token from the request
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(' ')
const bearerToken = bearer[1]
req.token = bearerToken
next()
} else {
res.sendStatus(401)
}
}
Now, this only checks to see if we have a token at all, it is a pretty simple check and is not in any way a security check.
This is because we may want to do more checks at the objectController.create function, about who this is, and what they should be able to do next. These checks could also be done within the above function, depending on your use case.
objectController.create receives the request, and the first thing it does is check the token
const currentUser = await currentUser(req.token)
which checks if this user is someone in the database, based on decoding from the jwt signing secret
async currentUser(token) {
const decoded = jwt.verify(token, process.env.USER_AUTH_SECRET) // << very important never to commit this as a readable value in your repo, store in a local environment variable
const user = await User.findOne({
where: { email: decoded.user.email },
})
console.log(user.email)
return user
},
So, whilst this is not a Passport solution, it hopefully shows the basic process of middleware and then authenticating a jwt, which I think was your general question.
I think you want something like this:
const fs = require('fs');
const swaggerTools = require('swagger-tools');
const path = require('path');
const jsYaml = require('js-yaml');
const passport = require('passport');
const cors = require('cors');
const CONSTANTS = require('./constants');
module.exports = function initializeSwagger(app) {
// swaggerRouter configuration
const swaggerRouterOptions = {
swaggerUi: path.join(__dirname, '/swagger.json'),
controllers: path.join(__dirname, '../controllers'),
useStubs: process.env.NODE_ENV === 'development' // Conditionally turn on stubs (mock mode)
};
const swaggerDoc = jsYaml.safeLoad(fs.readFileSync(path.join(__dirname, '../api/swagger.yaml'), 'utf8'));
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// *** Where you will call your JWT security middleware ***
app.use(initializeSwaggerSecurity(middleware));
//enable CORS
app.use(cors());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerRouterOptions));
// Serve the Swagger documents and Swagger UI
if(process.env.NODE_ENV === "prod"){
app.use('/docs', (req, res, next) => {
});
} else {
app.use(middleware.swaggerUi());
}
});
};
function initializeSwaggerSecurity(middleware) {
return middleware.swaggerSecurity({
jwtAuth: (req, authOrSecDef, scopes, callback) => {
passport.authenticate('jwt', {session: false}, (err, user, info) => {
if (err) {
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_DEFAULT))
};
if (!user) {
// no user session, were we tampered? What happened?
// #param info has that detail!
// console.log('url requested: ' + req.url + ' | raw headers: ' + req.rawHeaders);
// console.log('api: passport => jwt fn() initializeSwaggerSecurity(), rejected jwt token, token tampered or user session does not exist; failed to authenticate token: ', info);
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_TOKEN))
}
else {
req.user = user;
return callback();
}
})(req, null, callback);
}
});
};
Hope this helps you or anybody else who might be looking to implement this on Swagger 2.0

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.

How to mock Express JWT unless function?

I'm using Express and Express-JWT.
In a Express() instance I use:
const api = express()
api.use(jwt({
// my options
}))
To mock this in tests, I use a mocks\express-jwt\index.js file containing:
const jwt = jest.fn().mockImplementation(options => (req, res, next) => {
// set dummy req.user and call
next()
})
module exports = jwt
This all works fine.
Now I want to skip JWT for the root endpoint, so I changed the jwt usage to:
api.use(jwt({
// my options
}).unless({path:['/']}))
In my mock file I added:
jwt.unless = jest.fn().mockImplementation(options => (req, res, next) => {
next()
})
However, the tests now always fail with function unless is not defined.
Anyone an idea how to mock this unless behaviour?
unless is being used as a property on the result of calling jwt.
So to mock it, add your unless mock as a property of the function returned by your jwt mock:
const jwt = jest.fn().mockImplementation(options => {
const func = (req, res, next) => {
// set dummy req.user and call
next();
};
func.unless = jest.fn().mockImplementation(options => (req, res, next) => {
next();
});
return func;
});
module.exports = jwt;
Suggested answer by Brian did not work for me, because in the func method I do some stuff for faking an authorization check.
My problem was I needed to do skip the authorization check for the method+path given in by the unless function.
My solution now is like this:
const jet = jest.fn(options => {
let mockFunc = (req, res, next) => {
// get authorization from request
let token = ...
if (!token) {
res.status(401).send('No token provided')
} else {
req.token = token
next()
}
}
mockFunc.unless = jest.fn(args => (req, res, next) => {
if (args.method == req.method && arg.path == req.path) {
// not do authorization check for excluded endpoint via unless
next()
else {
mockFunc(req, res, next)
}
}
return mockFunc
}
Thanks to Brian for pointing me in the right direction.

How to update cookie containing jwt using passport authentication

I have a cookie containing a signed jwt that is valid for 5 mins. The jwt contains basic user info (for authentication), as well as a globally unique ID (guid). I store these guids in a database if they are valid, and on the next request after expiration of the jwt, I want to:
1.) Check the database for the guid and see that it is still valid (not blacklisted)
2.) Update the jwt within the cookie with a new 5minute validity and the same information
There are many errors I've run into, but nothing I've tried has worked, and I'm curious as to whether this is even possible or the correct approach at this point.
I am using the node js packages "passport-jwt" in conjunction with "jsonwebtoken" to create the jwts and verify them.
//////////////////////
//authorization.js
//////////////////////
const JWTStrategy = require('passport-jwt').Strategy;
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
require('../models/Guids');
const Guids = mongoose.model('Guids');
module.exports.JWTStrategy = function (passport) {
passport.use('jwt', new JWTStrategy({
jwtFromRequest: req => cookieExtractor(req, 'token'),
secretOrKey: 'secret',
passReqToCallback: true
},
(req, jwt_payload, done) => {
if (Date.now() / 1000 > jwt_payload.exp) {
Guids.findOne({ _id: jwt_payload.guid, userId: jwt_payload.uid })
.then(guid => {
if (guid.valid) {
//REFRESH TOKEN HERE
//????????return done(null, jwt_payload);
} else {
//FORCE USER TO RE-AUTHENTICATE
//???????return done('access token expired');
}
})
.catch(err => {
console.log(err);
return done('failed to validate user');
});
} else {
return done(null, jwt_payload);
}
}
));
};
var cookieExtractor = function (req, tokenName) {
var token = null;
if (req && req.cookies) {
token = req.cookies[tokenName];
} else {
console.log('no cookie found');
}
return token;
};
--
/////////////////////////////
//app.js
/////////////////////////////
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const passport = require('passport');
const jwt = require('jsonwebtoken');
const app = express();
require('./authorization').JWTStrategy(passport);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieParser());
//Protected Route
app.get('/xyz', passport.authenticate('jwt', {session: false}), (req, res) => {
//route logic....
});
//login Route (creates token)
app.post('/login', (req, res, next) => {
payload = {
guid: 12345678901010101',
uid: '123456789',
};
req.login(payload, { session: false }, (err) => {
if (err) {
console.log(err);
} else {
const token = jwt.sign(payload, 'secret', {expiresIn: '30s'});
res.cookie('token', token, { httpOnly: true });
res.redirect('/xyz');
};
}
}
const port = process.env.PORT || 5000;
const server = app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
By default, when the token expires, the authenticate middleware on the protected route immediately throws a failure.
I would like to bypass this failure and instead execute some code in the "authorization.js" file where the comment says "REFRESH TOKEN HERE".
That line of code is never even reached because of the automatic failure! I've tried console logging before and after expiration.
I have even manually bypassed the automatic failure before, but the response object (res) which contains the cookies is not available in the passport-jwt strategy. I am a little lost on where this logic should be implemented if the designated spot is nonsensical due to it being a middleware function.
Additionally, if the protected route is a POST, and the token expired after the page was "GET"ted successfully, I would like to not impede the POST method. I would like to seamlessly refresh the token, and then move along with the POST.
Passport provides support for custom messages or to be more precise for custom callbacks. You have to call the passport authenticate middleware manually by yourself embedded in your own wrapper middleware. This allows access to the req and res objects. For more details see the documentation.
If the built-in options are not sufficient for handling an authentication request, a custom callback can be provided to allow the application to handle success or failure.
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});

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