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

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.

Related

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) {
//...
}
}, [])

passport and serving files nodejs

i am using passport with google strategy for authentication
my folder structure:
views
home.html
enter.html (this has just one google+ button)
app.js
routes
auth.js (for google login)
i want the client to be directed to enter.html and not be able to use home.html if req.user is not set ( req.user is set when user is authenticated using google )
once authentication is done user should be redirected to home.html
app.use(express.static()) makes both of them available which is not what i want
the google login page comes by auth/google
and i also need to know what i should keep as the callback uri
in app.js
i have done mongodb configuration
i have done passport configuration
what to do next?
in auth.js
const router = require('express').Router();
const passport = require('passport');
router.route('/google')
.get(passport.authenticate('google', { scope: ["profile"] }));
router.route('/google/redirect')
.get(passport.authenticate('google'), (req, res, next) => {
// res.redirect what
});
module.exports = router;
To serve the home.html page you could redirect to a protected home route. Here an example of how I would go about implementing this.
auth.js
router.route('/google/redirect')
.get(passport.authenticate('google', { failureRedirect: '/' }), (req, res, next) => {
// Set to redirect to your home route / html page
res.redirect('/home')
});
To prevent users from going to home without authorization, you should also add a route guard to your /home route.
routes.js
const { checkAuth } = require('./guards'); // Protected routes
router.get('/home', checkAuth, async (req, res) => {
res.render('home')
});
guards.js
module.exports = {
checkAuth(req, res, next) {
if (req.isAuthenticated()) {
return next()
} else {
res.redirect('/')
}
},
}

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

passport.authenticate is not a function

Hello I am new in NodeJs and I have been following this tutorial http://code.tutsplus.com/tutorials/authenticating-nodejs-applications-with-passport--cms-21619 to create a app with authenticating.
I tried to follow all the structre and code from the tutorial (code is on github https://github.com/tutsplus/passport-mongo) but when I open my app in browser
i get error this error
TypeError: passport.authenticate is not a function
at module.exports (C:\myApp\routes\index.js:24:34)
This is my index.js route file
var express = require('express');
var router = express.Router();
var passport = require('passport');
var isAuthenticated = function (req, res, next) {
// if user is authenticated in the session, call the next() to call the next request handler
// Passport adds this method to request object. A middleware is allowed to add properties to
// request and response objects
if (req.isAuthenticated())
return next();
// if the user is not authenticated then redirect him to the login page
res.redirect('/');
}
module.exports = function(passport){
/* GET login page. */
router.get('/', function(req, res) {
// Display the Login page with any flash message, if any
res.render('index', { message: req.flash('message') });
});
/* Handle Login POST */
router.post('/login', passport.authenticate('login', {
successRedirect: '/home',
failureRedirect: '/',
failureFlash : true
}));
/* GET Registration Page */
router.get('/signup', function(req, res){
res.render('register',{message: req.flash('message')});
});
/* Handle Registration POST */
router.post('/signup', passport.authenticate('signup', {
successRedirect: '/home',
failureRedirect: '/signup',
failureFlash : true
}));
/* GET Home Page */
router.get('/home', isAuthenticated, function(req, res){
res.render('home', { user: req.user });
});
/* Handle Logout */
router.get('/signout', function(req, res) {
req.logout();
res.redirect('/');
});
return router;
}
Probabbly the problem is there, maybe routing was change in some version of express, but I cant figure out what is the problem.
Can you help pme please ?
I had same problem. Look at app.js. There must be:
var routes = require('./routes/index')(passport);
You have just put the parenthesis at the wrong place.
It should be
router.post('/login', passport.authenticate('login'), {
successRedirect: '/home',
failureRedirect: '/',
failureFlash : true
});

Pushing user data from Nodejs server to Angular after successful login

I try to login my user via Facebook with use of PassportJS and pass the user data to Angular.
On the server side it all looks ok with the following code for the Facebook callback in users controller:
exports.facebookCallback = function() {
return function(req, res, next) {
passport.authenticate('facebook', function(err, user, email) {
if (err || !user) {
return res.redirect('/auth');
}
req.login(user, function(err) {
if (err) {
return res.redirect('/auth');
}
return res.redirect('/');
});
})(req, res, next);
};
};
From what I understand from the PassportJS docs, calling req.login should put user data into the session.
My routes on the server side looks following:
app.get('/auth', usersCtrl.auth);
app.get('/auth/signout', usersCtrl.logout);
app.get('/auth/facebook', passport.authenticate('facebook', {
scope: ['email', 'user_hometown']
}));
app.get('/auth/facebook/callback', usersCtrl.facebookCallback());
express and passport configuration includes:
app.use(express.cookieParser());
app.use(express.session({secret: '1234567890QWERTY'}));
app.use(express.bodyParser());
app.use(passport.initialize());
app.use(passport.session());
Now on the angular side I try to get the user data from the session in a service defined like this:
module.exports = require('angular')
.module('HomeModule', [])
.controller('HomeCtrl', function ($scope) {
//home controller code ors here
}).controller('NavbarCtrl', ['$scope', 'Authentication', function ($scope, Authentication) {
$scope.authentication = Authentication;
//rest of the navbar controller goes here
}]).factory('Authentication', [
function() {
var _this = this;
_this._data = {
user: window.user
};
return _this._data;
}
]);
Unfoortunately, the user data is not available in window.user on angular side.
Any ideas what I'm doing wrong here?
As Girish said, the passport session object won't be available on client side. As you seem to be using express, a simple way to do this is to use express-expose.
If you want the user data to be available on all pages when the user is authenticated, you can add something like this before your routes declaration
app.use(function (req, res, next) {
if (req.isAuthenticated()) res.expose(req.user, 'user');
next ();
});
The user data will be available client side as window.user.
The passport session object won't be available on the window object , instead you need to get it from the server using some service or a redirect url.
After successful authentication , the primary route function will be called,
which, in this case, will redirect the user to the home page.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.get('/', function(req, res){
res.render('index', { user: req.user });
});
or you can create a route to get the logged in user data
app.get('/account', function(req, res){
if (req.isAuthenticated()) {
res.send({user : req.user});
}else{
res.redirect('/login');
}
});
On the Angular side, you can set the user data to rootscope from the $http response,
$rootScope.session = {}
$rootScope.session.user = res.user;

Categories

Resources