I am setting up a nodejs project with passportjs and passport-jwt. I see where you can specify passport.authenticate for each route you want to secure. However, I do not see a way to lock down all router except maybe login and register. I see where express-jwt allows for the use of express-unless, which seems to accomplish this functionality. Is there a similar mechanism for passport-jwt and if so how would this be accomplished?
Actually you don't even need express-unless you can use the fact that express allow to register middlewares that get executed all the time to do your filtering
const express = require('express');
const app = express();
function authenticateSomeRoutesMiddleware(req, res, next) {
if (/(login|register)/.test(req.originalUrl)) {
// No authentication needed
return next();
} else {
// Option 1 => use default passport logic
// which respond with a 401 unauthorized status if authentication fails
passport.authenticate('jwt', { session: false}), function(req, res, next) {
// Do something now you know that the user has been authenticated
return next(); // this will call the next middleware on the stack
})(req, res, next);
// Option 2: use a custom callback to allow your application
// to handle success or failure
// As per passport spec:
// - If authentication failed, user will be set to false.
// - If an exception occurred, err will be set.
// - An optional info argument will be passed, containing additional details
// provided by the strategy's verify callback.
passport.authenticate('local', function(err, user, info) {
if (err) {
// Error in authentication process; handle it or call...
return next(err);
}
if (!user) {
// Authentication failed (based on your strategy's implementation)
// You can for example try again
return res.redirect('/login');
}
// If you are using session to store the user call req.logIn() else call `return next()` directly
req.logIn(user, function(err) {
if (err) { return next(err); }
return next();
});
})(req, res, next);
}
}
// add this BEFORE your route definitions
app.use(authenticateSomeRoutesMiddleware);
// add all your routes here
app.use('/login', function(req, res, next) {
// do something
});
app.use('/register', function(req, res, next) {
// do something else
});
app.use('/some/protected/route', function(req, res, next) {
// this will get called once the authentication process has been cleared
});
//...
Related
I am building an API endpoint with Express, NodeJS, PassportJS, and TypeScript. I want to allow two types of authentication for this endpoint. SAML (for humans) and token for automation. For human authentication, I'm using the passport-saml strategy. For token auth I'm using passport-http basic authentication. So far my code looks like:
import session from "express-session";
const samlStrategy = getSamlStrategy();
const basicStrategy = getBasicStrategy();
app.use((req, res, next) =>
session({
// store sessions in db
})(req, res, next)
);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
//...
});
passport.deserializeUser((username, done) => {
//...
});
passport.use(samlStrategy);
passport.use(basicStrategy);
const requireApiAuth: express.Handler = (req, res, next) => {
if (req.user) {
next();
} else {
res.status(401);
}
};
const tryTokenAuth: express.Handler = (req, res, next) => {
if (!req.user && req.headers.authorization && req.headers.authorization.indexOf("Basic") > -1) {
passport.authenticate("basic", { session: false })(req, res, next);
} else {
next();
}
};
//...
// SAML endpoints here
//...
app.use(
"/api",
tryServiceUserAuth,
requireApiAuth,
The basic idea is the middleware function tryTokenAuth will check to see if a user is already present on the request. If there is, then that means a human has already logged in via SAML auth. If there is no user AND if the request specifies Basic authorization then we should use the basic strategy to authenticate. At the moment, this is working. I am able to authenticate using either strategy for the /api route.
The issue is that even though I specify {session: false} for basic authentication I'm STILL getting a session cookie sent back in the response. And a session is being recorded in my database. I don't understand what I need to configure to prevent this behavior. I do NOT want a session to be created when basic auth is used.
Is there any way to accomplish this?
As it turns out specifying session: false in the call to authenticate only prevents passport from adding its session data to a request session. The reason a session is still being created is because my config says:
app.use((req, res, next) =>
session({
// store sessions in db
})(req, res, next)
);
In order to prevent this when using basic auth I had to update to:
app.use((req, res, next) => {
if (req.headers.authorization && req.headers.authorization.indexOf("Basic") > -1) {
next();
} else {
session({
// store sessions in db
})(req, res, next)
}
});
I followed the documentation for passport.js with passport-local: http://www.passportjs.org/docs/authorize/
When I send my users to /login they are authenticated, but nowhere in that document can I find how to authorise my users.
I've tried this, but that gives me a bad request:
router.get('/somepage', passport.authenticate('local'), function(req, res, next) {
});
I'm looking for way to protect all my pages at once. I'm working with Express 4.16 and use different route files to split up my routes.
Sam
you can use middleware with a small trick to switch between strategies
example:
const allowUrl = ['public', 'nonprivate','home'];
const authenticationMiddleware = (whiteList =[]) => (req, res, next) => {
if(whiteList.find(req.baseUrl)) {
next();
}
if (req.isAuthenticated()) {
return next()
}
res.redirect('/');
}
app = express();
app.use(passort.initialize());
app.use(authenticationMiddleware(allowUrl));
app.use(apiRouter);
app.listen(3000, ()=> console.log('hello internet');
you can add your middleware code like below
router.get('/', isAuthenticated, function(req, res) {
//your next function
});
function isAuthenticated(req, res, next) {
// do any checks you want to in here
// CHECK THE USER STORED IN SESSION FOR A CUSTOM VARIABLE
// you can do this however you want with whatever variables you set up
if (req.user.authenticated)
return next();
// IF A USER ISN'T LOGGED IN, THEN REDIRECT THEM SOMEWHERE
res.redirect('/');
}
As I wanted ALL routes (except for login routes off course) to pass authorization, I solved it as follows:
var ensureAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) return next();
else res.redirect('/login')
}
// usersRouter contains all open routes like '/login':
app.use('/', usersRouter);
// From here on, all routes need authorization:
app.use(ensureAuthenticated);
app.use('/', indexRouter);
app.use('/api/foo', fooRouter);
app.use('/api/bar', barRouter);
I'm not sure what do you mean by "but nowhere in that document can I find how to authorise my users". Passportjs won't authorize any user. It is an authentication middleware. Authorization is different from authentication.
I think you are looking for application level middleware. You have to use app.use to make authentication work for each request to the server.
You can read more about it here. https://expressjs.com/en/guide/using-middleware.html#middleware.application
I want to execute a function each time when the express router is called.
I know I could have placed the function simply inside the app.get function, but I want to call the same function multiple times.
Here is my router.js file:
var Setting = require('../models/setting');
module.exports = function(app, passport) {
// =====================================
// HOME PAGE (with login links) ========
// =====================================
app.get('/', function(req, res) {
Setting.findOne(function(err, setting) {
if (err)
throw err;
// console.log(setting);
res.render('index', { title: 'eduBird | Reach the glory', setting: setting }); // load the index file
});
});
// =====================================
// LOGIN ===============================
// =====================================
// show the login form
app.get('/login', sabSettings, function(req, res) {
// render the page and pass in any flash data if it exists
res.render('login', {
message: req.flash('loginMessage'),
errors: req.flash('error'),
title: 'Login | eduBird',
setting: setting
});
});
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true
}));
// =====================================
// SIGNUP ==============================
// =====================================
// show the signup form
app.get('/signup', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('signup', {
message: req.flash('signupMessage'),
errors: req.flash('error'),
title: 'Register | eduBird',
setting: req.setting
});
});
// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect: '/profile',
failureRedirect: '/signup',
failureFlash: true
}));
// app.post('/signup', function(req, res) {
// console.log(req);
// });
// =====================================
// PROFILE SECTION =====================
// =====================================
// we will want this protected so you have to be logged in to visit
// we will use route middleware to verify this (the isLoggedIn function)
app.get('/profile', isLoggedIn, sabSettings, function(req, res) {
res.render('profile', {
user: req.user, // get the user out of session and pass to template
title: req.user.local.name + "'s profile | eduBird",
setting: req.setting
});
});
// =====================================
// LOGOUT ==============================
// =====================================
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
};
// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/login');
};
function sabSettings(next) {
Setting.findOne(function(err, setting) {
if (err)
throw err;
console.log('sabSetting function executed');
console.log(setting);
console.log('~~~~~~~~~~~');
// return setting;
return next(setting);
});
};
Here I had used an example of isLoggedIn which is executing fine, but the same is not able to work for sabSettings() which would pass all settings config from database to, my /login, /signup, /profile and/or / all the routes.
The console.log(setting) is returning all the data to my console, but I am getting an error stating:
throw er; // Unhandled 'error' event
^
TypeError: next is not a function
at C:\Users\animeshweb\Desktop\projects\eb-v2\routes\routes.js:106:16
You can see that I had embedded a function in app.get('/') for getting the same, but I want this function to be executed wherever I want, so I want a separate function for that.
Update
I update my routes.js as requested:
function sabSettings(req, res, next) {
Setting.findOne(function(err, setting) {
if (err)
next(err);
console.log('sabSetting function executed');
console.log(setting);
console.log('~~~~~~~~~~~');
req.setting = setting;
next();
});
};
I had also made Setting = require(myModelURL) above, which is working fine fo route.get'/'.
*This is my view/layout.pug file`
link(rel='icon', type='image/png', href=setting.logo.logo16 sizes='16x16')
link(rel='icon', type='image/png', href=setting.logo.logo32 sizes='32x32')
link(rel='icon', type='image/png', href=setting.logo.logo128 sizes='128x128')
The same is working fine in adminlayout0.pug
Thanks.
You've declared sabSettings wrong. Middleware is passed three arguments so your declaration should be like this:
function sabSetting(req, res, next) {
// function logic here
}
Since the argument you named next was in the wrong position, it was not a function when you tried to call it.
And, I don't know why you are trying to do return next(setting). That will tell Express that you're reporting an error in the request. If you're trying to put the setting value somewhere that the rest of the request handlers can use it, then you probably want to put it on the req object such as:
req.setting = setting; // put setting on the req object for other code to use
next(); // continue routing
In addition, you should never do a throw err inside a middleware function. That simply won't do anything useful and your request will probably never get finished. Instead, you should handle the error when you get it. You may either branch to some alternate strategy within the middleware when you get an error or you may simply returned a failed request with either next(err) or by doing res.status(500).send(...).
You will then need to change your res.render() to this:
res.render('index', { title: 'eduBird | Reach the glory', setting: req.setting });
The setting variable is now stored on the req object so that's where you need to refer to it.
Change it everywhere you are referring to setting.
js and Passport.js. So I have this :
router.post('/login', middleware1, middleware2);
I also have this on my app.js
passport.use(new passportLocal(
{ usernameField: 'email' },
function(email, password, done) {
// working logic here
// this returns a user object
// to the middleware1 if query is ok
// returns error otherwise.
}
));
And here are my middlewares
middleware1 = function (req, res, next) {
passport.authenticate('local', function(error, user, message){
// How do I get the 'user' object and pass it
// to the outer req or res?
req.user = user; // <- is this ok?
})(req, res, next);
req.user = user;
next();
}
middleware2 = function (req, res, next) {
console.log("user ", req.user); // <- this will obviously print null or undefined
}
Now. How do I pass the user object from inside the passport.authenticate() to middleware2. I'm thinking of using promise something like this passport.authenticate().then() but I dont know the syntax. Please help. Thanks.
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
authenticate()'s function signature is standard Connect middleware, which makes it convenient to use as route middleware
Why are you wrapping the passport.authenticate() in another function?
I'm not sure what you are trying to do, but I would probably start without the misdirection
router.post('/login', passport.authenticate('local'), middleware2(req,res,next));
function middleware2(req,res,next) {
console.log("user ", req.user);
}
If you want the req object passed to your function set the passport js options 'passReqToCallback: true '
I'm trying to add authentication middleware that should prevent access to part of the site:
app = express()
.get('/api/test', function (req, res) { ... })
.use('/api', function (req, res, next) {
if (req.param('key')) {
next();
} else {
res.json(401, {
message : 'Authentication failed'
});
res.end();
}
})
.get('/api/data', function (req, res) { ... });
And my expectation that calls to the /api/data will be first processed by the key checker and then (if it is successful) by the /api/data handler. But instead the request processed by the '/api/data' first.
It seems that the checker works for the /api/something_that_does_not_exist, but not for /api/something_that_exist.
Maybe I missed something in express/connect documentation?
Update I've tracked this up to the fact that the first get/post call initializes the router middleware so it is executed first.
Once you declare a route, Express inserts the router middleware into the middleware stack at that point in setting up the app.
In your case, because you insert .get('/api/test', ...) before you insert your key checking middleware, the router middleware gets inserted and will take precedence (also for the /api/data route you declare later) and your key checker is never called.
Here are two solutions:
// separate middleware, used for all routes that need checking
var keyChecker = function(req, res, next) {
...
};
app.get('/api/test', function(req, res) { ... });
app.get('/api/data', keyChecker, function(req, res) { ... });
// or, as an alternative, create a 'catch-all' route between the routes that don't
// need to be checked, and the ones that should; this will also match non-existing
// routes (like '/api/foobar'), which might or might not be an issue;
app.get('/api/test', function(req, res) { ... });
app.all('/api/*', function(req, res, next) { // 'all' means 'all methods'
// keychecker code
});
app.get('/api/data', function(req, res) { ... });
A third solution could be to explicitly check for /api/test in the key checking middleware itself (req.path === '/api/test'), and just call next() if it matches.