passportJS authentication for all routes - javascript

I'm using passportJS for protecting API Endpoints in an Express APP.
The following is working fine.
app.get("/route1",
passport.authenticate('basic', { session: false }),
(req, res) => { //something });
However, I'm adding more routes and don't want to repeat that passport.authenticate for every new route I create.
I.e.
app.get("/route2..N",
passport.authenticate('basic', { session: false }),
(req, res) => { //something });
I understand that this is a middleware and that I should be able to do this, but I haven't found any examples.

passport.authenticate just returns a middleware function so:
app.use(passport.authenticate('basic', { session: false });
app.get("/route1", (req, res) => { /* something */ } )

Related

Nodejs app suddenly started to act differently

This part of code handles the login authorization routing in my app
const express = require("express");
const authController = require('../controllers/authController');
const indexController = require('../controllers/indexController');
const router = express.Router();
router.use("/login", (req, res, next) => {
if(req.session.loggedIn) {
res.redirect('/account');
}
next();
});
router.use("/account", (req, res, next) => {
if(!req.session.loggedIn) {
res.redirect('/login');
}
next();
});
router.get('/', (req, res) => {
res.redirect('/login');
});
router.get('/login', (req, res) => {
res.render('login');
});
router.get('/log-out', authController.logOut);
router.get("/account", indexController.getAccountData);
module.exports = router;
There were no problems and it was working fine till recent days.
I haven't change anything in this file nor authController nor indexController.
When I make a change (in other parts), nodemon restarts the app and I automatically jump to login page cause obviously all sessions are destroyed. But I get an error in getAccountData function (Error says req.session is undefined).
As you can see there's no way for the app to reach that function with no sessions set.
I have to restart the app again to act correct.
The session will be cleared each time the server restarts. So to escape from that you need to save the session to database. If you are using mongodb or I can give you example using mongodb.
import MongoStore from "connect-mongo";
import session from "express-session";
app.use(
session({
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URI || "mongodb://localhost:27017/project",
}),
secret: "secret key",
cookie: { maxAge: sessionExpireInMilliseconds },
})
);
req.session.loggedIn would error out whenever session is undefined. You need to check if it is defined before trying to access loggedIn.
Try:
router.use("/login", (req, res, next) => {
if(req.session && req.session.loggedIn) {
res.redirect('/account');
}
next();
});
router.use("/account", (req, res, next) => {
if(!req.session || !req.session.loggedIn) {
res.redirect('/login');
}
next();
});

Where should I generate the CSRF token?

I'm using csurf to handle CSRF tokens in my express application, but I don't know where I'm supposed to create the token. I can't use the sign-in route, because the req.csrfToken() function is not available.
app.use(csrf({ cookie: true }))
app.post('/signin', function (req, res) {
// Authentication ...
res.cookie('XSRF-TOKEN', req.csrfToken()); // Not possible (post request)
})
Should I create a new route for this that I use every time a user opens the front-end of my website?
app.use(csrf({ cookie: true }))
app.get('/csrf', function (req, res) {
res.cookie('XSRF-TOKEN', req.csrfToken());
})
Thanks in advance!
Edit: My frontend (react) is separate from the backend (express server)
There are several ways that you can set the CSRF Token:
You can use it when rendering forms
JS:
var csrfProtection = csrf({ cookie: true })
app.get('/form', csrfProtection, function (req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() })
});
HTML
<input type="hidden" name="_csrf" value="{{csrfToken}}">
You can use it as a cookie for all the routes. Recommended for Single Page Applications:
app.all('*', function (req, res) {
res.cookie('XSRF-TOKEN', req.csrfToken())
res.render('index')
})
If you have a separate backend and frontend, you can use a middleware like below:
JS:
app.use(express.csrf());
app.use(function(req, res, next) {
res.locals._csrf = req.csrfToken();
next();
});
HTML:
input(type='hidden', name='_csrf', value=_csrf)
More info about this : http://sahatyalkabov.com/jsrecipes/#!/backend/csrf-protection-with-express
I ended up using the following code:
Express server:
app.use(cookieParser());
app.use(csrf({ cookie: true }));
app.get('/csrf', (req, res, next) => {
res.send({'csrf_token': req.csrfToken()});
});
App component in react:
useEffect(async () => {
try {
const res = await axios.get('http://api.myserver.com/csrf', { withCredentials: true });
const csrfToken = (await axios.get('http://api.myserver.com/csrf')).data.csrf_token;
// save csrfToken to redux store and include it in every request (-> axios interceptor)
} catch (err) {
//...
}
}, [])

How to automate next() call in every route function? (express.js)

Hi I am facing the problem that I need to log each incomming request and the associated responses in my database. My current solution looks like the following:
./routes/customer.js
router.get('/', async (req, res, next) => {
req.allCustomers = await fetchAllCustomers();
res.status(200).send(req.allCustomers);
next(); // <- this is my personal problem
});
./middleware/logging.js
module.exports = function (req, res, next) {
db.query(
`INSERT INTO logging SET ?`,
{
request: JSON.stringify([req.body, req.params]),
response: JSON.stringify(req.response)
}
);
}
routes declaration
module.exports = function(app) {
app.use(express.json());
app.use('/api/customers', customers); // <- ROUTE ./routes/customer.js
app.use(logging); // <- MIDDLEWARE ./middleware/logging.js
}
I already mentioned my problem in my first piece of code. It is really repetitive to call next() in every route manually and I would like to avoid this. I already tried to load the middleware before all routes, call next() in the middleware function and execute my db query afterwards but I do not have the response at this point because of the async functionality.
Is there any way to handle this situation or will I need keep calling next() at the end of each route function?
If you don't want to call next() from your routes, you cannot have middleware run after them. It needs to be placed before. But can you get the response inside a middleware that runs before the route? The answer is yes!
It may be a little hacky, but since your route uses res.send(), you can use that to your advantage. By running before your route, your middleware can hijack that res.send function, to make it do other stuff.
./routes/customer.js
router.get('/', async (req, res, next) => {
req.allCustomers = await fetchAllCustomers();
res.send(req.allCustomers); // We'll hijack this
});
./middleware/logging.js
module.exports = function (shouldBeLoggedFunc) {
return function (req, res, next) {
if (shouldBeLoggedFunc(req)) {
// Store the original send method
const _send = res.send;
// Override it
res.send = function (body) {
// Reset it
res.send = _send;
// Actually send the response
res.send(body);
// Log it (console.log for the demo)
console.log(`INSERT INTO logging SET ?`, {
request: JSON.stringify([req.body, req.params]),
response: JSON.stringify(body)
});
};
}
next();
};
};
routes declaration
function shouldBeLogged(req) {
// Here, check the route and method and decide whether you want to log it
console.log(req.method, req.path); // e.g. GET /api/customers
return true;
}
module.exports = function(app) {
app.use(express.json());
app.use(logging(shouldBeLogged)); // <- Place this before your routes
app.use('/api/customers', customers);
};
when you use express.Router class like you already did and then use this code
app.use('/api/customers', customers);
you don't have to write 'next()' inside callback function in router.get .
there is an example
create a router file named birds.js in the app directory, with the following content:
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})
module.exports = router
Then, load the router module in the app:
var birds = require('./birds')
// ...
app.use('/birds', birds)

adding additional data to req.user with passport-google-oauth

I have a route which meant to authenticate the user using google oauth passport strategy ,(/auth/google) I also want to pass additional data as query in the url in that route (/auth/google?someParam=SOME_PARAM) , this data I want to add to req.user by the time I get it back from google in (/auth/google/callback). The problem is that I have access to this query through /auth/google but google will redirect me to /auth/google/callback which dont have access to this data anymore.
note - Because of design limitation I cant do it with external source as database.
passport-google docs
CODE :
// auth.js
router.get(
"/",
(req, res, next) => {
let siteName = req.query.siteName;
let pageName = req.query.pageName;
console.log("siteName", siteName);
return next();
},
passport.authenticate("google", {
scope: ["https://www.googleapis.com/auth/plus.login"]
})
);
module.exports = router;
// authCb.js
router.get(
"/",
passport.authenticate("google", {
scope: ["https://www.googleapis.com/auth/plus.login"],
failureRedirect: "/"
}),
(req, res) => {
console.log(req.user);
res.send(req.user);
}
);
module.exports = router;
// app.js
app.use("/auth/google", auth);
app.use("/auth/google/callback", authCb);
You have to store your params in session before sending auth request to google.
Then, after redirect, get your params back from session.
// auth.js
router.get(
"/",
(req, res, next) => {
req.session.lastQuery = req.query;
return next();
},
passport.authenticate("google", {
scope: ["https://www.googleapis.com/auth/plus.login"]
})
);
module.exports = router;
// authCb.js
router.get(
"/",
passport.authenticate("google", {
scope: ["https://www.googleapis.com/auth/plus.login"],
failureRedirect: "/"
}),
(req, res) => {
const { lastQuery } = req.session;
console.log(lastQuery);
}
);
module.exports = router;
You should refer to Google's documentation. You can use the "state" parameter to pass any data you want to get back once the user is back to your site. This is the main use of this parameter.
You can see the details here.

Trouble with authentication

I´m facing a confusion issue while implementing the authentication for my restful api using passport local strategy.
Note:
I got the authentication working successfully when I´m doing it all in my index.js. But I want to use in Classes for better Code separation.
I have a passport.js Module
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var mysql = require('mysql');
var dbconfig = require('./database');
var connection = mysql.createConnection(dbconfig.connection);
module.exports = function(passport) {
// passport needs ability to serialize and unserialize users out of session
passport.serializeUser(function (user, done) {
//console.log("SER");
console.log(user),
done(null, user);
});
passport.deserializeUser(function (user, done) {
console.log("XXXX");
console.log(user);
connection.query("SELECT * FROM users WHERE name = ? ",user.name, function(err, rows){
console.log("DER");
console.log(rows);
done(err, rows[0]);
});
});
// passport local strategy for local-login, local refers to this app
passport.use('local-login', new LocalStrategy(
function (username, password, done) {
console.log("hhh");
console.log(username);
connection.query("SELECT * FROM users WHERE name = ? ",username, function(err, rows){
console.log(rows);
return done(err, rows[0]);
});
})
);
// route middleware to ensure user is logged in
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
};
This is my Controller Class:
class AuthenticateController {
constructor(router, passport) {
this.router = router;
this.registerRoutes();
this.passport = passport;
}
registerRoutes() {
this.router.post('/login/:username/:password', this.login.bind(this));
//this.router.get('/logout', this.logout.bind(this));
this.router.get('/content', this.content.bind(this));
}
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
res.redirect("/content");
}
content(req, res ) {
console.log(req.user);
if (req.isAuthenticated()) {
res.send("Congratulations! you've successfully logged in.")
} else {
res.sendStatus(401);
}
}
isLoggedIn(req, res, next) {
console.log(req.user);
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
}
module.exports = AuthenticateController;
The Controller gets the router and passport fully configured as parameters from my index.js.
//index.js
var express = require('express')
, cors = require('cors')
, app = express()
, passport = require('passport')
, morgan = require('morgan');
require('./config/passport')(passport); // pass passport for configuration
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(require('express-session')({secret: 'vidyapathaisalwaysrunning',
resave: true,
saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
var apiRouter = express.Router();
app.use('/api', apiRouter);
//
var apiV1 = express.Router();
apiRouter.use('/v1', apiV1);
var authenticateApiV1 = express.Router();
apiV1.use('/auth', authenticateApiV1);
var AuthenticateController = require('./controllers/authenticate');
var ac = new AuthenticateController(authenticateApiV1, passport); //pass in our fully configured passport
//If I call this /login instead of the /auth/login/ in the Controller Class it works!
//app.post("/login",
// passport.authenticate("local-login", { failureRedirect: "/login"}),
// function (req, res) {
// res.redirect("/content");
// });
What is working and what is not working
The Authentication in general is working. In my posted index.js you see app.post("/login", .... If I call this one the authentication is successfully and if I try to reach the restricted content in /auth/content/ req.user has a value (the user object) and I can successfully call req.isAuthenticated() .
BUT, If I use the authentication from /auth/login/username/password the req.user is undefined when trying to reach the restricted Content.
I get no error and the response of /auth/login/username/password/ HTTP Code 301 - 'redirecting to /content.
I have currently no idea what I´m doing wrong here and I´m pretty new to the topic of Node/express/ passport ..
Hope someone has an Idea. If you need something else to help me, just mention it in the comments and I will do my best to provide you everything you need.
Thanks
EDIT:
I recently tried to read the req.user in the login function and even there it is undefined
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
console.log(req.user) //undefined
res.redirect("/content");
}
I guess it could be some async problem and I should use some callback functions, but I don´t know how to apply this in my login()
EDIT 2:
Another Issue I´m facing is the integration of the isLoggedIn() request.
If I do this:
registerRoutes() {
this.router.get('/', this.isLoggedIn, this.getUsers.bind(this));
this.router.get('/:id', this.getSingleUser.bind(this));
}
it results in 401 - Unauthorized
A console.log(req.user); in the isLoggedIn() results in undefined.
But if I call the first route without calling isLoggedIn() and do console.log(req.user); the user object exists.
The correct use of callback with passport authentication for local strategy can be as below:
function(req, res, next){
passport.authenticate('local-login', function(err, user, info){
if(err)
return logger.log('error', err);
if(user)
req.login(user, function(err){
if(err) return next(err);
return res.json({'success': true});
});
if(!user)
return res.json({'error':true, 'message': info.message, 'type': info.type});
})(req, res, next);
}
Please note the use of req.login() to explicitly set user in session.
The only thing I'm finding strange is that your route declarations are different.
In the AuthenticateController the route is declared as:
this.router.post('/login/:username/:password', ...
While in index.js, the route is simply declared as
app.post("/login", ...
How is your client submitting the login credentials to the server? If it is by form, like the tutorial, could it be that having :username and :password declared as route params but being sent by form messes with passport?
Try registering the route exactly like index.js
this.router.post('/login', ...
EDIT:
I've found another dicrepancy. In AuthenticateController the res.redirect("/content"); is not wrapped inside a callback. So it is being executed before Authenticate finishes running.
In the index.js example, passport is being used as a route middleware:
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
res.redirect("/content");
});
While in the passport.js it is inside the callback. Consider declaring it in the route:
registerRoutes() {
this.router.post('/login', this.passport.authenticate("local-login", { failureRedirect: "/login"}), this.login.bind(this));
(...)
}
login(req, res) {
res.redirect("/content");
}
O, better yet, why not use passport's option to declare both success and failure redirects, since that seems to be all that you are doing:
login(req, res) {
this.passport.authenticate("local-login", { successRedirect: "/content", failureRedirect: "/login" });
}
You are passing this.login.bind(this) as a middleware to this.router.post('/login/:username/:password', this.login.bind(this)); but login(req, res) only responds to the request with res.redirect("/content"); i.e. redirecting to /content
So like you said, you need to supply a callback that does something with the user that is returned from passports middleware verify callback.
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
console.log(req.user); // log user in console
res.json({user: req.user}); // send user as json response
});
The custom callback mentioned by #divsingh is if you want to explicitly have control of setting the session, error messages and redirecting the request. Any other information can be found under http://passportjs.org/docs

Categories

Resources