First off if I am asking a obviously stupid question I apologies in advance.
I have a passport authentication strategy setup currently and it is working ok. The implementation is as follows.
Authentication strategy (authentication.js) :
const passport = require("passport");
const passportJWT = require("passport-jwt");
const params = {
//Params here
};
module.exports = function LocalStrategy() {
let strategy = new Strategy(params, function (payload, done) {
//Logic here
});
passport.use(strategy);
return {
initialize: function () {
return passport.initialize();
},
authenticate: function () {
return passport.authenticate("jwt", {
session: false
});
}
};
};
Using in a route :
const localAuth = require('./authentication/LocalStrategy')();
app.get('/test', localAuth.authenticate(), (req, res) => {
res.json(req.isAuthenticated());
});
In the server.js file
const localAuth = require('./authentication/LocalStrategy')();
app.use(localAuth.initialize());
I am planning to use multiple authentication strategies in a single route and I found this implementation. But rather than having the authentication strategy written in the same server.js I want to have the strategy written in an external file (in my case authentication.js) and refer the strategy in the route as
passport.authenticate(['SOME_OTHER_STRATEGY', 'jwt']
How can I implement this?
Ok apparently I was't trying hard enough,
I didn't have to do any change to my current logic other than serializeUser and deserializeUser. and just use:
passport.authenticate(['SOME_OTHER_STRATEGY', 'jwt'])
that't it.
This is a small middleware that i created to skip authentication during tests on my nodejs app:
authentication(auth) {
if (process.env.NODE_ENV !== 'test') {
return jwt({
secret: new Buffer(auth.secret, 'base64'),
audience: auth.clientId
});
} else {
return (req, res, next) => { next(); };
}
}
I am not happy with the way it looks. Is there a more elegant way of accomplishing this ?
I think you are right to not be happy with the way that looks. I think what you really want to be able to do is mock out your authentication from the test code instead of inside your actual application code. One way to do this is via proxyquire.
Then a very simple test could look something like this if app.js requires authentication via var authentication = require('./lib/authentication')
var proxyquire = require('proxyquire');
var app = proxyquire('./app.js', {
'./lib/authentication': function() {
// your "test" implementation of authentication goes here
// this function replaces anywhere ./app.js requires authentication
}
});
it('does stuff', function() { ... });
I'm using Passport to protect the front and back end of a MEAN stack app. The app is structured like so:
monstermash
config // server configuration
public // static directory that will serve the entire Angular frontend
app
index.js // initialization of the server
models
index.js // mongoose schemas and models
passport
index.js // configuration for passport and all my strategies
routes
index.js // basic route definitions for the API (using functions defined under v1, below) and UI (routes defined inline here for simplicity's sake)
v1
index.js // all the functions called to power the API routes
Here's app/index.js since I know it's sometimes a matter of calling application middleware in the right order:
var express = require('express');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var mongoose = require('mongoose');
var app = express();
var CONFIG = require('config').BASE;
var bcrypt = require('bcrypt-nodejs');
var passport = require('passport');
var flash = require('connect-flash');
var models = require('./models');
app.passport = require('./passport');
app.port = CONFIG.PORT;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(allowCrossDomain);
app.use(express.static('public'));
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
var routes = require('./routes');
app.use(express.static('public', {redirect:false}));
routes(app)
module.exports = app
passport/index.js looks like this. A lot of the commented-out bits are only cut out in an effort to get this down to bare bones for debugging:
var models = require('../models')
passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, LocalAPIKeyStrategy = require('passport-localapikey-update').Strategy;
passport.use('localapikey', new LocalAPIKeyStrategy(
{apiKeyHeader:'x-auth-token'},
function(apikey, done) {
console.log('api key');
models.User.findOne({ apikey: apikey }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user);
});
}
));
passport.use('local-signup', new LocalStrategy(
function (req, username, password, done) {
console.log('trying local');
models.User.findOne({
local: {username: username}, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
console.log('no user');
return done (null, false);
}
if (!user.validPassword(password)) {
console.log('bad pwd');
return done(null, false);
}
return done (null, user);
}
})
}
));
module.exports = passport;
The localaipkey strategy is included here just to illustrate that it works and is configured in much the same way as the local-signup one.
Then my routes/index.js looks like this. HTML for login forms is inline here because this is just a perliminary test. Note that I'm not doing anything other than checking validation. Including one of the API routes here too do demonstrate how that's set up. The UI code here is lifted straight from a Passport tutorial, since I went back to the drawing board and got rid of my own code on the matter.
var v1 = require('./v1');
// API routes as an example. This authentication is called before the route and works fine.
module.exports = function(app) {
/* API: V1 */
app.route('/v1/monster/:id')
.put(
app.passport.authenticate('localapikey', { session: false }),
v1.monster.update)
.delete(
app.passport.authenticate('localapikey', { session: false }),
v1.monster.delete
);
// My test login routes. Here, authenticate is called inside the route because it's the handler for logging in.
app.route('/login')
.post(
function (req, res) {
console.log(req.body);
app.passport.authenticate('local-signup', {
successRedirect: '/root',
failureRedirect: '/fail'
});
})
.get(function (req,res) {
res.send('<!-- views/login.ejs -->\
<!doctype html>\
<html>\
<head>\
<title>Node Authentication</title>\
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->\
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->\
<style>\
body { padding-top:80px; }\
</style>\
</head>\
<body>\
<div class="container">\
\
<form action="/login" method="post">\
<div>\
<label>Username:</label>\
<input type="text" name="username"/>\
</div>\
<div>\
<label>Password:</label>\
<input type="password" name="password"/>\
</div>\
<div>\
<input type="submit" value="Log In"/>\
</div>\
</form>\
\
</div>\
</body>\
</html>');
});
So that form submits a POST:/login request with the form data. The form body is there in req.body but the console.log messages I have in the validation function never get logged. The form submission just hangs and hangs; there's no res.send() on that route because the authentication should either pass or fail and never get to that, but the whole app.passport.authenticate() function is just totally bypassed.
I've done a lot of trial-and-error on this, and I've found that if I call app.passport.authenticate() with the name of a strategy that isn't even registered, the same thing happens: there's no failure message, it just continues on with the route like it wasn't even there. So maybe the issue is that this is happening and it's not recognizing the local-signup strategy being registered, though I don't know why that would be and the localapikey strategy is found.
Side note, I actually am testing this with a username and password set in the form; I found one SO question on that from someone who was trying empty or passwordless submissions and not seeing their validation function execute, so I'm sure it's not that.
So, the answer to my question is basically "because you can't call the authenticate function inside a route."
I'm not deleting the question because I know that I got that idea from some Passport tutorial somewhere, therefore someone else may fall prey to the same thing later.
I'm using sessions and cookies to authenticate the users. I would like to check for users having a cookie and if so i will set the sessions variables.
So basicly what i do is :
Check if sessions variables exist
If not, check if user has cookie
If he has a cookie, I compare the value in my database.
If everything's ok, I set up the session.
Now i'd like to have that process into a module so i don't have to paste that code into each routes of my site.
Let's say I've put all that code in a middleware route located at routes/middleware/check_auth.js.
How do I export this module so I can check in my route page if the user has auth or not, something like :
//routes/index.js
var check_auth = require('./middleware/check_auth');
module.exports = function(app){
app.get('/', check_auth, function(req, res){
if(variable_from_check_auth == true){
res.render('index_with_auth');
}else{
res.render('index_without_auth');
}
});
};
Btw, I'm not sure if it's the right way to do or if I simply have to :
Call the module on each routes.
Check for some sessions variables before rendering.
If someone could help me!
You can just export your middleware as simple as this(assuming you are using express session handler and cookie parser):
var userModel = require('./user');
module.exports = function check_auth(res, req, next) {
if (!res.session) {
req.send(401);
return;
}
userModel.isAuthenticated(req.session.id, function (result) {
if (!result) {
req.send(401);
return;
});
next();
});
};
I am using express to make a web app in node.js. This is a simplification of what I have:
var express = require('express');
var jade = require('jade');
var http = require("http");
var app = express();
var server = http.createServer(app);
app.get('/', function(req, res) {
// Prepare the context
res.render('home.jade', context);
});
app.post('/category', function(req, res) {
// Process the data received in req.body
res.redirect('/');
});
My problem is the following:
If I find that the data sent in /category doesn't validate, I would like pass some additional context to the / page. How could I do this? Redirect doesn't seem to allow any kind of extra parameter.
There are a few ways of passing data around to different routes. The most correct answer is, of course, query strings. You'll need to ensure that the values are properly encodeURIComponent and decodeURIComponent.
app.get('/category', function(req, res) {
var string = encodeURIComponent('something that would break');
res.redirect('/?valid=' + string);
});
You can snag that in your other route by getting the parameters sent by using req.query.
app.get('/', function(req, res) {
var passedVariable = req.query.valid;
// Do something with variable
});
For more dynamic way you can use the url core module to generate the query string for you:
const url = require('url');
app.get('/category', function(req, res) {
res.redirect(url.format({
pathname:"/",
query: {
"a": 1,
"b": 2,
"valid":"your string here"
}
}));
});
So if you want to redirect all req query string variables you can simply do
res.redirect(url.format({
pathname:"/",
query:req.query,
});
});
And if you are using Node >= 7.x you can also use the querystring core module
const querystring = require('querystring');
app.get('/category', function(req, res) {
const query = querystring.stringify({
"a": 1,
"b": 2,
"valid":"your string here"
});
res.redirect('/?' + query);
});
Another way of doing it is by setting something up in the session. You can read how to set it up here, but to set and access variables is something like this:
app.get('/category', function(req, res) {
req.session.valid = true;
res.redirect('/');
});
And later on after the redirect...
app.get('/', function(req, res) {
var passedVariable = req.session.valid;
req.session.valid = null; // resets session variable
// Do something
});
There is also the option of using an old feature of Express, req.flash. Doing so in newer versions of Express will require you to use another library. Essentially it allows you to set up variables that will show up and reset the next time you go to a page. It's handy for showing errors to users, but again it's been removed by default. EDIT: Found a library that adds this functionality.
Hopefully that will give you a general idea how to pass information around in an Express application.
The easiest way I have found to pass data between routeHandlers to use next() no need to mess with redirect or sessions.
Optionally you could just call your homeCtrl(req,res) instead of next() and just pass the req and res
var express = require('express');
var jade = require('jade');
var http = require("http");
var app = express();
var server = http.createServer(app);
/////////////
// Routing //
/////////////
// Move route middleware into named
// functions
function homeCtrl(req, res) {
// Prepare the context
var context = req.dataProcessed;
res.render('home.jade', context);
}
function categoryCtrl(req, res, next) {
// Process the data received in req.body
// instead of res.redirect('/');
req.dataProcessed = somethingYouDid;
return next();
// optionally - Same effect
// accept no need to define homeCtrl
// as the last piece of middleware
// return homeCtrl(req, res, next);
}
app.get('/', homeCtrl);
app.post('/category', categoryCtrl, homeCtrl);
I had to find another solution because none of the provided solutions actually met my requirements, for the following reasons:
Query strings: You may not want to use query strings because the URLs could be shared by your users, and sometimes the query parameters do not make sense for a different user. For example, an error such as ?error=sessionExpired should never be displayed to another user by accident.
req.session: You may not want to use req.session because you need the express-session dependency for this, which includes setting up a session store (such as MongoDB), which you may not need at all, or maybe you are already using a custom session store solution.
next(): You may not want to use next() or next("router") because this essentially just renders your new page under the original URL, it's not really a redirect to the new URL, more like a forward/rewrite, which may not be acceptable.
So this is my fourth solution that doesn't suffer from any of the previous issues. Basically it involves using a temporary cookie, for which you will have to first install cookie-parser. Obviously this means it will only work where cookies are enabled, and with a limited amount of data.
Implementation example:
var cookieParser = require("cookie-parser");
app.use(cookieParser());
app.get("/", function(req, res) {
var context = req.cookies["context"];
res.clearCookie("context", { httpOnly: true });
res.render("home.jade", context); // Here context is just a string, you will have to provide a valid context for your template engine
});
app.post("/category", function(req, res) {
res.cookie("context", "myContext", { httpOnly: true });
res.redirect("/");
}
use app.set & app.get
Setting data
router.get(
"/facebook/callback",
passport.authenticate("facebook"),
(req, res) => {
req.app.set('user', res.req.user)
return res.redirect("/sign");
}
);
Getting data
router.get("/sign", (req, res) => {
console.log('sign', req.app.get('user'))
});
we can use express-session to send the required data
when you initialise the app
const express = require('express');
const app = express();
const session = require('express-session');
app.use(session({secret: 'mySecret', resave: false, saveUninitialized: false}));
so before redirection just save the context for the session
app.post('/category', function(req, res) {
// add your context here
req.session.context ='your context here' ;
res.redirect('/');
});
Now you can get the context anywhere for the session. it can get just by req.session.context
app.get('/', function(req, res) {
// So prepare the context
var context=req.session.context;
res.render('home.jade', context);
});
Here s what I suggest without using any other dependency , just node and express, use app.locals, here s an example :
app.get("/", function(req, res) {
var context = req.app.locals.specialContext;
req.app.locals.specialContext = null;
res.render("home.jade", context);
// or if you are using ejs
res.render("home", {context: context});
});
function middleware(req, res, next) {
req.app.locals.specialContext = * your context goes here *
res.redirect("/");
}
You can pass small bits of key/value pair data via the query string:
res.redirect('/?error=denied');
And javascript on the home page can access that and adjust its behavior accordingly.
Note that if you don't mind /category staying as the URL in the browser address bar, you can just render directly instead of redirecting. IMHO many times people use redirects because older web frameworks made directly responding difficult, but it's easy in express:
app.post('/category', function(req, res) {
// Process the data received in req.body
res.render('home.jade', {error: 'denied'});
});
As #Dropped.on.Caprica commented, using AJAX eliminates the URL changing concern.
Update 2021:
i tried url.format and querystring and both of them are deprecated, instead we can use URLSearchParams
const {URLSearchParams} = require('url')
app.get('/category', (req, res) =>{
const pathname = '/?'
const components ={
a:"a",
b:"b"
}
const urlParameters = new URLSearchParams(components)
res.redirect(pathname + urlParameters)
})
I use a very simple but efficient technique
in my app.js ( my entry point )
I define a variable like
let authUser = {};
Then I assign to it from my route page ( like after successful login )
authUser = matchedUser
It May be not the best approach but it fits my needs.
app.get('/category', function(req, res) {
var string = query
res.redirect('/?valid=' + string);
});
in the ejs you can directly use valid:
<% var k = valid %>