JWT verification for Swagger NodeJs API middleware - node.js

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

Related

How to authenticate keycloak token using node js that calls postgraphile?

I'm new on node js, and the company that i work for needs a proof of concept about postgraphile, the situation is this:
I created a node js mini server that uses postgraphile to access the data on postgres
The mini server works fine and can return data and also can use mutations.
I used keycloak-connect to try to access keycloak to authenticate the token from the request that is sent by postman but there is a problem.
If the token is valid or not it does not matter for the mini server, the only thing that seems to matter is that is a bearer token.
I tried to use other plugins (like keycloak-nodejs-connect, keycloak-verify, etc) but the result is the same, i also changed my code to use the examples in the documentation of those plugins but nothing.
This is my code: (keycloak-config.js file)
var session = require('express-session');
var Keycloak = require('keycloak-connect');
let _keycloak;
var keycloakConfig = {
clientId: 'type credential',
bearerOnly: true,
serverUrl: 'our company server',
realm: 'the test realm',
grantType: "client_credentials",
credentials: {
secret: 'our secret'
}
};
function initKeycloak(){
if(_keycloak){
console.warn("Trying to init Keycloak again!");
return _keycloak;
}
else{
console.log("Initializing Keycloak...");
var memoryStore = new session.MemoryStore();
_keycloak = new Keycloak({store: memoryStore}, keycloakConfig);
return _keycloak;
}
}
function getKeycloak(){
if(!_keycloak){
console.error('Keycloak has not been initialized. Please called init first');
}
return _keycloak;
}
module.exports = {
initKeycloak,
getKeycloak
};
My Index.js file:
const express = require('express')
const bodyParser = require('body-parser')
const postgraphile = require('./postgraphile')
const app = express()
const keycloak = require('../config/keycloak-config').initKeycloak()
var router = express.Router();
app.set( 'trust proxy', true );
app.use(keycloak.middleware());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(postgraphile);
app.get('/', keycloak.checkSso(), (req, res) => {
res.send('success');
} );
var server = app.listen(8080, () => console.log(`Server running on port ${8080}`));
Also I used this code to get the token and use the keycloak-verify plugin but got nothing:
router.get('/',keycloak.protect(),function(req, res, next) {
var token=req.headers['authorization'];
console.log(token);
try {
let user = keycloak.jwt.verify(token);
console.log(user.isExpired());
} catch (error) {
console.error(error);
}
})
I know that I lack the knowledge because I am a backend (C#) developer, can somebody help me with this?, thanks in advance.
I found the answer to my problem:
const express = require("express");
const request = require("request");
var keycloakConfig = require('../AuthOnly/config/keycloak-config').keycloakConfig;
const postgraphile = require('./postgraphile');
const app = express();
const keycloakHost = keycloakConfig.serverUrl;
const realmName = keycloakConfig.realm;
// check each request for a valid bearer token
app.use((req, res, next) => {
// assumes bearer token is passed as an authorization header
if (req.headers.authorization) {
// configure the request to your keycloak server
const options = {
method: 'GET',
url: `${keycloakHost}/auth/realms/${realmName}/protocol/openid-connect/userinfo`,
headers: {
// add the token you received to the userinfo request, sent to keycloak
Authorization: req.headers.authorization,
},
};
// send a request to the userinfo endpoint on keycloak
request(options, (error, response, body) => {
if (error) throw new Error(error);
// if the request status isn't "OK", the token is invalid
if (response.statusCode !== 200) {
res.status(401).json({
error: `unauthorized`,
});
}
// the token is valid pass request onto your next function
else {
next();
}
});
} else {
// there is no token, don't process request further
res.status(401).json({
error: `unauthorized`,
});
}});
app.use(postgraphile);
app.listen(8080);

How to use csurf within a custom middleware?

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);
};

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);
});

Authorization in a NodeJS Express REST server

I am building a REST server in nodejs using express.
I would like to allow certain users to perform certain calls.
i.e. have an admin who can edit other users and see reports, where a user can only perform simple actions.
I was trying to use passport.js and passport-ldapauth, and also I would like to perform different queries for authentication (check credentials) and authorization (check if the user is part of a group).
var fs = require('fs');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
var app = express();
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Allow self signed certificates
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
app.use('/', index);
app.use('/users', users);
var OPTS = {
server: {
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
}
},
handleErrorsAsFailures: true,
failureErrorCallback: (err) => console.log(err)
};
passport.use(new LdapStrategy(OPTS));
passport.use('test', new LdapStrategy(OPTS));
app.use(passport.initialize());
app.post('/login', function(req, res, next) {
passport.authenticate('ldapauth', function(err, user, info) {
if (err) return next(err);
if (!user) return res.status(401).send(info);
res.json(user);
// req.logIn(user, function(err) {
// if (err)
// console.error(err);
// if (err) return next(err);
// return res.json(user);
// })
})(req, res, next);
});
The passport-ldapauth strategy does not allow you to perform any additional checks or queries as far as I know from reading over the documentation. The strategy and Passport in general is aimed at making the login/authentication process seamless and easy as possible. So any additional constraints will need to be handled on your own.
With that said, passport-ldapauth utilizes ldapauth-fork underneath which in turn uses ldapjs. You can try to utilize ldapjs as shown here and here, but I think the easiest solution would be to use ldapauth-fork directly.
We first need to set up ldapauth-fork, so we'll use the following example app/ldap/index.js:
const LdapAuth = require('ldapauth-fork')
const ldap = new LdapAuth({
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
})
ldap.on('error', (err) => { throw err })
module.exports = ldap
Our example app/controllers/auth.js could look something like this:
const jwt = require('jsonwebtoken')
const ldap = require('../ldap')
const { User } = require('../database/models') // mongoose model
const Promise = require('bluebird')
exports.login = async (req, res) => {
const { username, password } = req.body
if (!username || !password) {
res.status(400
res.json({ error: 'Missing username or password.' })
return
}
// ldapauth-fork doesn't support Promises.
// You can try to promisfy it, but I prefer this.
// I've named it `profile`, but you can name it whatever you want.
const profile = await Promise.fromCallback(cb => ldap.authenticate(username, password, cb))
// Since this is a REST API, we need to send back a token.
// For this example, we're creating it by hand.
const token = jwt.sign({ user: profile }, 'secret', {})
// Use epoch time from the token instead of generating it ourselves.
const { exp } = jwt.verify(token, 'secret')
// Finally send the token.
// By convention, the keys are snake case.
res.json({
access_token: token,
token_type: 'Bearer',
expires_in: exp,
user: profile
})
}
Now that we have our token created, we need a way to verify that token. To do that we need to write our own middleware. So for example app/middleware/valid-token.js:
const jwt = require('jsonwebtoken')
exports.needsAdminAccess = (req, res, next) => {
// This token should have already been validated by the `requiresToken` middleware
let token = req.header('authorization').split(' ')[1]
token = jwt.verify(token, 'secret')
// Let's check if they are in the admin group
// Remember that we set the user/profile value in the controller.
if (!token.user.dn.includes('ou=ADMIN')) {
next(new Error('You must be an admin to access this route.'))
return
}
// Any additional checks would go here.
// ...
// If everything is fine then call next to let the request continue.
next()
}
exports.requiresToken = (req, res, next) => {
// Assuming the token is in the header as Authorization: Bearer token
let token = req.header('authorization').split(' ')[1]
// Make sure our secret key matches
token = jwt.verify(token, 'secret')
// Additional checks of the token should be done here as well.
// ...
// Don't forget to call next if all is good
next()
}
Finally we use the middleware wherever you define your routes, for example:
const express = require('express')
const app = express()
const { requiresToken, needsAdminAccess } = require('./middleware/valid-token')
// This route needs a valid token, but not admin rights
app.get('/user', requiresToken, (req, res) => { })
// This route needs a valid token AND admin rights
app.get('/admin', requiresToken, needsAdminAccess, (req, res) => { })
I wrote everything from scratch to hopefully paint a clear picture on how everything works. You can use other packages to validate the token for you, but we need to do verify specific things so we write our own.

ExpressJS authorization middleware get executed even for the routes above it

I am working on a university/student project with full MEAN stack. We have NodeJS, ExpressJS backend and Angular2 frontend. Backend runs on localhost:8080 and frontend runs on localhost:4200
This is how my backend looks like
var express = require('express'),
...
var app = express();
...
// needed because of cross origin resource sharing during development
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
var port = process.env.PORT || 8080;
var loginController = require('./controllers/loginController')(Person);
var personController = require('./controllers/personController')(Person, Transaction);
var transactionController = require('./controllers/transactionController')(Person, Transaction);
var apiRouter = express.Router();
apiRouter.post('/login', loginController.authenticate);
/**
* Middleware that handles authorization of particular routes.
* Every request which starts with `/api/persons` or `/api/transactions`, will be intercepted and validated against JWT.
*/
apiRouter.use(function (req, res, next) {
// JWT gets validated
});
apiRouter.get('/persons', personController.fetchAllPersons);
apiRouter.get('/persons/:personId', personController.fetchPersonById);
apiRouter.get('/persons/:personId/transactions', personController.fetchTransactionsByPersonId);
apiRouter.post('/transactions', transactionController.addNewTransaction);
app.use('/api', apiRouter);
app.listen(port, function () {
console.log('Listening on port: ' + port);
});
I read that the routes of the express router get executed sequentially, so the order of middleware inclusion is important. Therefore I set it after /login route since it should not be available without JWT authorization.
When I start the application and execute requests with postman everything works as it is supposed, but when I try to login from the frontend middleware function gets executed also for login route but it shouldn't, right?
Is is maybe because of they are running on different ports or could it be an issue caused by cross origin, I really have no idea?
Did anyone already face similar issue and could you please explain this behavior?
Thx
EDIT 1:
As robertklep has mentioned below, it might depend on the loginController implementation and the way how we handle login in frontend, here are the code snippets
jwt = require('jsonwebtoken');
var loginController = function (Person) {
var authenticate = function (req, res) {
req.person = req.body;
var searchPerson = { username: req.person.username }
Person.findOne(searchPerson, function (err, person) {
if (err) throw err;
if (!person) {
return res.status(401).send({ message: 'Wrong username/password' });
}
person.comparePasswords(req.person.password, function (err, isMatch) {
if (err) throw err;
if (!isMatch) {
return res.status(401).send({ message: 'Wrong username/password' });
}
var token = jwt.sign(person._id, /* XXX SECRET XXX */);
res.status(200).json({
token: token,
person: person
});
});
});
};
return {
authenticate: authenticate
};
};
module.exports = loginController;
Frontend:
...
export class LoginComponent implements OnInit {
/**
* validates input data and on successful validation,
* tries to login the user with her/his credentials
*/
public login(): void {
// validation logic should consider all fields, no matter if the user has entered any data
this.validate(false);
// no validation error, continue with login process
if(!this.errorMessage){
const form = this.loginForm;
var credentials = {
name: form.get('name').value,
password: form.get('password').value
};
this.loginService.login(credentials.name,credentials.password)
.subscribe(
name => this.router.navigate(['welcome']),
error => this.errorMessage = error);
}
}
}
...
export class LoginService {
...
public login(userName: string, password: string): Observable<string> {
var person = {
'username': userName,
'password': password
};
return this.http.post('http://localhost:8080/api/login',person)
.map(this.extractToken)
.catch(this.handleError);
}
}
EDIT 2:
As asked, I am posting my middleware, so this is how it looks like
apiRouter.use(function(req, res, next) {
// first, check if a token is provided
var token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
// token gets validated by jwt lib
jwt.verify(token, /* SECRET */, function (err, decoded) {
if (err) {
// token invalid, respond with unauthorized
res.status(401).json({
message: 'Failed to authenticate token.'
});
} else {
// token is valid, continue to the particular request handler
req.decoded = decoded;
next();
}
});
} else {
// token was not provided, respond with forbidden
res.status(403).json({
message: 'No token provided.'
});
}
});
Like I said, when I use postman client and execute login request, everything works as expected (Middleware doesn't execute for the login request) but when I start our frontend app and do the same, middleware function gets executed.

Resources