I am running into an issue with Passport.js in which I want to get the currently logged in users information from a Post request and process some stuff. When I console.log(req.user) it comes up as 'undefined'. The set up and authentication all works, I can also retreive user's info using a Get request as seen from the first code snippet.
router.get('/', function(req , res){
console.log("The current logged in user is: " + req.user.first_name);
res.render('index.ejs' , {
user: req.user
});
});
^ returns user's name as expected
router.post('/testPost' ,function(req , res){
console.log("The current logged in user is: " + req.user);
res.json({
status: "success"
});
});
^returns undefined even when the user is logged in.
I have seen the same question raised here How to get req.user in POST request using passport js two years ago but there was no answer.
It's because the user might not be logged in at the time you're checking it.
To ensure that a user is logged in when accessing a route you should have a middleware that checks it for you.
You can write it as a separate module and import it in every single one of your routes if it's required.
The module:
module.exports = {
EnsureAuthenticated: (req, res, next) => {
if (req.isAuthenticated()) {
return next();
} else {
res.sendStatus(401);
}
}
};
The routes:
//Destructuring | EnsureAuth Function
const {
EnsureAuthenticated
} = require('../path/to/the/module');
//You should get your user here
router.get('/', EnsureAuthenticated, (req, res) => {
console.log(req.user)
});
Related
I've recently switched to Cognito for the authentication on my website. I'm using NodeJS/Express to run the server. With each API call having a callback in between the declaration and the main function to check the if the user is authenticated, currentAuthenticatedUser(), this should return a cognito user object for the current req/session.
What I've found is that this function works fine until someone logs in. If I log in like normal, my account then becomes authenticated, I gain access to the main page etc.
But then if I send someone else the website link, they automatically bypass the login because this function returns my user object, as opposed to filtering by request or session and replying with not authenticated. This seems like too basic of an issue to be right, in the sense that the Amplify function will return the user object of whoever is the last to sign in and authenticate, regardless of the auth status of any new incoming requests from elsewhere.
The two auth checks:
function checkAuthenticated(req, res, next) {
auth.currentAuthenticatedUser()
.then(user => {
console.log(user);
next();
})
.catch(err => {
console.log(err);
return res.redirect('/login');
});
}
function checkNotAuthenticated(req, res, next) {
auth.currentAuthenticatedUser()
.then(user => {
console.log(user);
return res.redirect('/');
})
.catch(err => {
console.log(err);
next();
});
}
Examples of API calls:
app.get('/', checkAuthenticated, (req, res) => {
res.render('index.ejs');
});
app.post('/login', checkNotAuthenticated, async (req, res, next) => {
// Some code to login
});
Any guidance is appreciated.
We have a route & a middleware like this:
When we do them like this:
//middleware
router.use((req, res, next) => {
// check header or url parameters or post parameters for token
let token = req.body.token || req.query.token || req.headers['x-access-token'],
// verifies secret and checks exp
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Failed to authenticate token.'
});
} else {
// if everything is good, save to request for use in other routes
res.locals.test = "test";
req.somevariable = "variable1";
console.log("res.locals.test inside middleware ", JSON.stringify(res.locals.test));
console.log("req.somevariable inside middleware ", JSON.stringify(req.somevariable));
next();
}
});
next();
});
//TestRoute
router.get('/TestRoute', (req, res) => {
console.log("res.locals.test outside middleware ", JSON.stringify(res.locals.test));
console.log("req.somevariable outside middleware ", JSON.stringify(req.somevariable));
});
The values of req.somevariable and res.locals.test are undefined outside middleware
When we do them like this:
//middleware
router.use((req, res, next) => {
res.locals.test = "test";
req.somevariable = "variable1";
console.log("res.locals.test inside middleware ", JSON.stringify(res.locals.test));
console.log("req.somevariable inside middleware ", JSON.stringify(req.somevariable));
next();
});
//TestRoute
router.get('/TestRoute', (req, res) => {
console.log("res.locals.test outside middleware ", JSON.stringify(res.locals.test));
console.log("req.somevariable outside middleware ", JSON.stringify(req.somevariable));
});
The values of req.somevariable and res.locals.test are available outside middleware.
What is the problem here?
res.locals.VARIABLENAME = req.user
VARIABLENAME (whatever variable you set it to) can show up as undefined when you use .ejs or when dynamic rendering with that variable, you can get errors like your variable name is undefined.
THIS IS AN ODDITY with res.locals: when you want to use the variable name you set to res.locals with rendering, you have to define req.user in your function THAT RENDERS THE FILE!
Backend: res.locals.currentUser = req.user
//THIS FUNCTION WILL RENDER PERFECTLY LETTING ME USE currentUser in my .ejs file
upgradeForm(req, res, next) {
let saved = req.user;
if(req.user){
res.render("users/upgrade");
} else{
req.flash("notice", "You've successfully signed in!");
res.redirect("/");
}
}
HOWEVER
//THIS FUNCTION WILL SAY currentUser is UNDEFINED IN MY .ejs file
upgradeForm(req, res, next) {
if(req.user){
res.render("users/upgrade");
} else{
req.flash("notice", "You've successfully signed in!");
res.redirect("/");
}
}
No code difference except for that the working function rendering has req.user set to a variable (you don't even have to use that variable). Took me 9 hours to solve this problem but now you know!
I am trying to add SAML authentication using passport-saml for my application. I am using Angular for routing. On loading of the homepage, I check for a user through a GET request to my node server "/auth" to check req.user, if it's true, I send back logged in user data; if false, I send back an error message. I have data binding on this user data response so in the event of no user data, I still show "Log in" button.
On click of the "Log in" button, I perform a redirect to the node server "/login". This does passport.authenticate function which runs and then ties in with "/assert" when it's complete to perform a redirect.** It is at this point I am running into a problem.** My redirect always goes to "/" because I am performing a redirect and using angular routing so I dont know how to store this route say "/myPage1" or "/myPage2". NOTE: I do not want to always send back to "/myPage1", I want to be able to "Login" from any view in the SPA.
Am I going about this in the wrong way? Is this a limitation of using Angular routing?
Angular
$scope.login = function () {
window.location.href = 'http://example.com/login';
};
function getCreds() {
$http.get('http://example.com/auth', { withCredentials: true })
.then(function (response) {
$scope.creds = response.data;
});
}
Node.js
app.get('/login', passport.authenticate('saml', { failureRedirect: '/login' }));
app.post("/assert", passport.authenticate('saml', { failureRedirect: '/login' }), function (req, res) {
// console.log('session' + JSON.stringify(req.session));
// console.log("original url " + req.session.originalUrl);
if (req.session.originalUrl) {
res.redirect(req.session.originalUrl);
}
else {
res.redirect('/');
}
});
app.get('/auth', function (req, res) {
var authenticated = req.user;
if (authenticated) {
res.send(req.user);
} else {
res.send({statusCode: 401, message: 'User session is not alive'});
}
});
I have found a way to achieve the desired effect.
Before making the "Login" call, I make another api call to "/setSessionRedirectUrl" which passes the current window.location.href from angular to node server for session storage.
app.post('/setSessionRedirectUrl', function (req, res) {
req.session.samlLoginRedirectUrl = req.body.redirectUrl;
res.send();
});
Then when the assert function is being run after login, it is looking for the req.session.samlLoginRedirectUrl to redirect to. It works!
I've hit a bit of a problem getting client-session middleware working in Express. In short the session_state doesn't seem to be accessible when redirecting to new route after being set. For reference I have followed this video tutorial (client-session part starts 36:00 approx.) and double checked my steps but still encountering problems. Middleware is set up as follows:
var sessions = require('client-sessions');
Instantiated with code from the Express website.
app.use(sessions({
cookieName: 'session',
secret: 'iljkhhjfebxjmvjnnshxhgoidhsja',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5
}));
I have the sessions middleware placed between bodyParser and routes if that makes any difference.
Here are the sections my routes/index.js pertaining to the issue. The req.session_state seems to get set ok and the correct user details log to the console.
// POST login form
router.post('/login', function(req, res) {
User.findOne( { email: req.body.email }, function(err,user){
if(!user) {
console.log("User not found...");
res.render('login.jade',
{ message: 'Are you sure that is the correct email?'} );
} else {
if(req.body.password === user.password) {
// User gets saved and object logs correctly in the console
req.session_state = user;
console.log("session user...", req.session_state);
res.redirect('/dashboard');
}
}
//res.render('login.jade', { message: 'Invalid password'} );
});
});
However, something is going wrong when the res.redirect('/dashboard'); is run because the session_state is not accessible when it hits that route. Here is the code for the /dashboard route.
router.get('/dashboard', function(req, res) {
// OUTPUT = 'undefined' ???
console.log("dash...", req.session_state);
// 'if' fails and falls through to /login redirect
if(req.session && req.session_state){
console.log("dash route...", req.session_state);
User.findOne( { email: req.session_state.email }, function
(err, user){
if(!user){
req.session.reset();
res.redirect('/login');
} else {
res.locals.user = user;
res.render('dashboard.jade')
}
});
} else {
res.redirect('/login');
}
//res.render('dashboard', { title: 'Your Dashboard' });
});
Basically, the object stored in session_state is not accessible after the /dashboard redirect. I've been trying to debug it for a day or so without any luck. Any help much appreciated. Sorry if I am missing something obvious. Just getting my feet wet with session middleware so maybe I haven't fully grasped Session or I am overlooking something. Thanks in advance!
I've updated my answer with code that should help you set the cookie and an alternative session manager known as a token. In this example I've provided to parts to a middle ware one part which attaches the cookie (this can be expanded on to determine your use case) and the second part which checks the token for expiration or what ever else might be in there (i.e. audience, issuer, etc.)
app.use('/js/',function(req, res, next) {
//check if the request has a token or if the request has an associated username
if(!req.headers.cookie){
console.log('no cookies were found')
var token = jwt.sign({user_token_name:req.body.user_name},app.get('superSecret'), {
expiresIn: 1 *100 *60 // expires in 1 mintue can be what ever you feel is neccessary
});
//make a token and attach it to the body
// req.body.token = token // this is just placing the token as a payload
res.cookie('austin.nodeschool' , token,{ maxAge: 100000, httpOnly: true }) //this sets the cookie to the string austin.nodeschool
}
if(req.body.user_name){
next()
}else{
res.send('request did not have a username').end() // this is here because my middleware also requires a username to be associated with requests to my API, this could easily be an ID or token.
}
},function(req, res, next) {
// console.log(req.headers) this is here to show you the avilable headers to parse through and to have a visual of whats being passed to this function
if(req.headers.cookie){
console.log(req.headers.cookie) //the cookie has the name of the cookie equal to the cookie.
var equals = '=';
var inboundCookie = req.headers.cookie
var cookieInfo = splitCookie(inboundCookie,equals) //splitCookie is a function that takes the inbound cookie and splits it from the name of the cookie and returns an array as seen below.
console.log(cookieInfo)
var decoded = jwt.verify(cookieInfo[1], app.get('superSecret'));
console.log(decoded)
// You could check to see if there is an access_token in the database if there is one
// see if the decoded content still matches. If anything is missing issue a new token
// set the token in the database for later assuming you want to
// You could simply check if it's expired and if so send them to the login if not allow them to proceed through the route.
}
next()
});
Suppose I have a script like this, which uses a Passport authentication strategy with an Express backend. How would I use this script to actually make API function calls? I don't see any explicit examples in the linked project's documentation nor can I find anything in Passport.js's documentation. Thanks.
After passport user serialization done, every request has user field, which contains information passed to done callback of passport.serializeUser method.
app.get('/userID', function (req, res) {
if (req.isAuthenticated()) {
res.json(req.user.id);
}
res.redirect('/login');
}
Also, you have access to session
app.get('/auth/fitbit/callback',
passport.authenticate('fitbit', { failureRedirect: '/login' }),
function(req, res) {
req.session.loggedInAt = Date.now();
res.redirect('/');
});
Information stored in session available in all requests, while user is authenticated
app.get('/someroute', function (req, res) {
// call to another service
var url = 'http://superservice.com/' + req.user.id + '/' + req.session.loggedInAt
http.get(url, function (_res) {
res.send(_res.data)
});
});
I'm supposing that you know how to use passport, and you will figure it out what's the right Fitbit API endpoint (honestly, I'm don't know it). Said that, let me give an idea that might help you solve your problem:
// An awesome npm module (https://github.com/mikeal/request)
var request = require('request');
//
//
//
// An express route.
app.get('/activities', function (req, res) {
if (req.user !== null) {
// User is authenticated.
getUserActivities(req.user.id, res);
} else {
// Redirect to login the user isn't authenticated.
res.redirect('/login');
}
});
// This function will make API calls to Fitbit
// using the User ID we got from the PassportJS
// authentication process.
function getUserActivities(id, res) {
// It will request from Fitbit User activities.
request('https://api.fitbit.com/1/user/'+ id +'/activities/',
function (error, response, body) {
if (!error && response.statusCode == 200) {
// If everything goes well.
return res.send(body);
} else {
// If something wrong happens.
return res.send(error);
}
);
}
The goal of this example is to show you that you need to use PassportJS to get fitbit users ID, then use that id to make API calls to fitbit.