I have an express application where I'm using passportjs for managing authentication.
At the moment I only want to support sign in with google in my application. I have implemented this properly and is working fine. Now, I want to restrict access to to the application to only those users who are registered with the application.
Registration is done by admin so there can only be few users who are registered. At the moment, anyone with a google account is able to login to the application. Instead of this, I would like the authentication to happen with google using OAuth2.0 and I would like to check if the user exists in my database before letting the user login to the system.
Update
Following is my code as requested
var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
var config = require('./config');
var directoryService = require('./services/directory');
/**
* Authentication configuration
*/
module.exports = function (app, router) {
passport.serializeUser(function (user, done) {
//I want to know if this is the correct process
directoryService.findResidentByEmailAddress(user.emails[0].value).then(function (data) {
if (data == null || data.length == 0) {
done({ 'status': 'Invalid Login' });
} else {
done(null, data);
}
});
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use(new GoogleStrategy({
clientID: config.auth.clientID,
clientSecret: config.auth.clientSecret,
callbackURL: config.auth.callbackURL,
passReqToCallback: true
},
function (request, accessToken, refreshToken, profile, done) {
process.nextTick(function () {
return done(null, profile);
});
}
));
app.use(passport.initialize());
app.use(passport.session());
router.use(function (req, res, next) {
if (req.isAuthenticated() || req.url.startsWith('/auth/')) {
return next();
}
res.redirect('/auth/login.html');
});
router.get('/auth/google',
passport.authenticate('google', { scope: ['email'] }));
router.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function (req, res) {
res.redirect('/');
});
router.get('/logout', function (req, res) {
req.logout();
res.redirect('/auth/login.html');
});
}
Related
I'm writing one of my first applications in NodeJS so please bear with me. I've managed to successfully authenticate a user to our Active directory and I can see the connect.sid cookie being set and used on the subsequent requests.
Upon debugging the application by dumping the req object I can also see that the user variable has been set successfully. From the documentation I've read that seems to be a criteria for a successful session match?
However, the request is still getting a 401 Unauthorized.
To summarize:
The user is successfully authenticated after posting credentials /login.
Upon successful authentication the user is redirected to "/".
The "/" path replies with 401 Unauthorized.
Any ideas much appreciated. Code below.
const express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var passport = require('passport')
var ActiveDirectoryStrategy = require('passport-activedirectory')
// Setup the authentication strategy
passport.use(new ActiveDirectoryStrategy({
integrated: false,
ldap: {
url: 'ldap://myad.company.com',
baseDN: 'DC=domain,DC=company,DC=com',
username: 'user',
password: 'password'
}
}, function (profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, 'Group', function (err, isMember) {
if (err) return done(err)
return done(null, profile)
})
}));
passport.serializeUser(function(user, done) {
done(null, JSON.stringify(user));
});
passport.deserializeUser(function(user, done) {
done(null, JSON.parse(user));
});
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(session(
{ secret: "password" }
));
app.use(passport.initialize());
app.use(passport.session());
// For debugging purposes
app.use(function (req, res, next) {
console.log(req)
next()
})
// The login page posts a form containing user and password
app.get("/login", (req, res) => {
res.sendFile(__dirname + '/public/index.html');
})
// Handler for the login page. Receives user and password and redirects the user to /
app.post('/login',
passport.authenticate('ActiveDirectory', {
failWithError: true,
successRedirect: "/",
failureRedirect: "/login"
}
), function(req, res) {
res.json(req.user)
}, function (err) {
res.status(401).send('Not Authenticated')
}
)
// This is where the issue happens. The page returns "Unauthorized".
// Using console.log(req) shows that the user property has been set to the req object.
// However, for some reason it still fails.
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Found what I did wrong!
The .authenticate method is only used to validate credentials, not to validate a session.
So this:
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Should become:
app.get('/', function(req, res, next) {
// This is verifying that the user part has been populated,
// which means that the user has been authenticated.
if (req.user) {
res.send('Returning with some text');
} else {
// If the user property does no exist, redirect to /login
res.redirect('/login');
}
});
Another thing that I changed was the serialize/deserialize functions:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
This removes redundant serializing/deserializing.
These articles really helped me understand the flow:
http://toon.io/understanding-passportjs-authentication-flow/
https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive
Hope it helps someone else!
/Patrik
I'm trying to implement facebook login with vue-authenticate and passport.
I succeeded in logging into my Facebook account. And i got the 'Callback code' successfully.
This is my callback url
http://localhost:8080/auth/callback?code=AQD0FgQ7I2oci0m3bqOHOBE1EV3Ri1TBnVcqs2PRT8pFNa38NIMX-eYiSr2EiWKQBMoNq1yOeo1QkDG1OiDjF_xUduK-HWMlMazsaBzoGNxiAK3FQH5KQopZ9NUnM2g-UYLpihtpsaFrRVssJkm8Xue1nyKbbWX76EPnPCIEVOfGM_JE4mbENLpp6_w8gwkTS9n8dtsNptDM72UO9zE7mj34J8Yls0A1VqmoZail0J2zwu4hJCzAzbP2FZ531Vo2tCERn2F_4DKsJ-zq_ppZWxRlKuRW9WFBL0UvsuNN_ODiRFs70P3SoK85-xHwzHJvx8VrVxmLlp5x7rVOzy2E2Jma#=
So I used axios to pass the 'Callback code' to the server. because my server code(passport-facebook) is this:
router.route('/auth/facebook/callback').get(passport.authenticate('facebook', {
successRedirect : '/',
failureRedirect : '/'
}));
and axois code in Vue is
this.$http.get('/api/users/auth/facebook/callback',{
params:{
code : this.param
}
})
.then((response) => {
this.movies = param;
})
but it never works.. I don't know why. just wondering, i chaneged axios code to get('api/users/'). and wrote server code like this
router.get('/',()=>{
console.log("good");
});
it works. I can see the 'good' message in console.
Let me know how you implement social sign-in!
if you want to configure Facebook login with passport So you can simply follow these Steps As mention below:
Step 1. Add passport configuration
const passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());
app.get('/success', (req, res) => res.send("You have successfully logged in"));
app.get('/error', (req, res) => res.send("error logging in"));
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
Step 2. Setup facebook configuration
const FacebookStrategy = require('passport-facebook').Strategy;
const FACEBOOK_APP_ID = 'your app id';
const FACEBOOK_APP_SECRET = 'your app secret';
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, cb) {
return cb(null, profile);
}
));
app.get('/auth/facebook',
passport.authenticate('facebook'));
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/error' }),
function(req, res) {
res.redirect('/success');
});
This will fix your issue
Please refer this link https://www.sitepoint.com/passport-authentication-for-nodejs-applications/
I am building a passport-github auth to my application. but I think currently I don't know how to extract the cookie from request that would say user is already logged in. so everytime When i go to home page i get redirected to /login.
My code roughly looks like this:
passport.use(new GitHubStrategy({
clientID: authConfig.GITHUB_CLIENT_ID,
clientSecret: authConfig.GITHUB_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:8080/auth/github/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
return db.user.findOne({where:{github_id:profile.id}})
.then(data=>{
if (data) {
return done(null,data);
} else {
return db.user.build({ github_id: profile.id }).save()
.then(()=>{
return db.user.findOne({where:{github_id:profile.id}})
})
.then(data=>{
return done(null,data);
})
}
});
}
));
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing
passport.serializeUser(function(user, done) {
console.log("serialize>>>>>", user.github_id);
done(null, user.github_id);
});
passport.deserializeUser(function(id, done) {
console.log("deserialize>>>>", id);
db.user.findOne({where:{github_id: id}})
.then(user=>{
done(null, user.toJSON());
})
});
I have established the session :
app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
And I have an isAuthenticated function that checks for req info:
function isAuthenticated (req, res, next) {
// If the user is logged in, continue with the request to the restricted route
console.log("req.user is>>>>", req);
if (req.isAuthenticated()) {
return next();
}
// If the user isnt' logged in, redirect them to the login page
return res.redirect("/index");
}
I am using this passport-github lib. I cannot get some useful information from reqseems
updated to include routes:
Here is the routes:
const isAuthenticated = require('./middleware/isAuthenticated.js');
router
.get('/index', query.renderIndex)
.get('/', isAuthenticated, query.displayRepos)
.post('/', query.queryRepoTopic)
.post('/trending', query.addRepo)
.post('/addTopic', query.addTopic)
.get('trending', query.updateScore);
router.get('/login', auth.loginPage)
.get('/auth/github',
passport.authenticate('github', { scope: [ 'user:email' ] }),
function(req, res){}
)
.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
auth.signInRedirect
)
.get('/logout', auth.logout);
Here is the controller function that does the logic:
const loginPage = (req, res) => {
res.render('index');
}
// signin a user in
const signInRedirect = (req, res) => {
console.log("here in callback>>>");
console.log("req.user is>>>>", req.user);
//res.json("you have successfully logged in!");
res.redirect('/');
}
const logout = (req, res) => {
req.logout();
res.redirect('/index');
}
I see you have this route configuration:
const isAuthenticated = require('./middleware/isAuthenticated.js');
router
.get('/index', query.renderIndex)
.get('/', isAuthenticated, query.displayRepos)
...
If you want to call localhost:3000, and be redirected to auth/github when you are not logged in, you could change isAuthenticated function like this:
function isAuthenticated (req, res, next) {
// If the user is logged in, continue with the request to the restricted route
console.log("req.user is>>>>", req);
if (req.isAuthenticated()) {
return next();
}
// If the user isnt' logged in, redirect them to the github login page.
return res.redirect("/auth/github");
}
Wich means, when you try to call the '/', the isAuthenticated will check if the req.user was set (if (req.isAuthenticated())), if not, redirect to the /auth/github route.
Have you tried this?
Have it can help!
I'm having a problem I'm not able to resolve. I'm developing an app with nodejs, using mongodb, expressjs and passportjs as my authentication middleware.
I currently have 3 strategies: facebook, twitter and instagram. What I want to achieve is that when a user login for the first time, if the user is logged with one strategy and logs in with another one save the profiles into the same mongodb user document.
This is my auth/index.js:
require('./local/passport').setup(User, config);
require('./facebook/passport').setup(User, config);
require('./twitter/passport').setup(User, config);
require('./instagram/passport').setup(User, config);
var router = express.Router();
router.use('/local', require('./local'));
router.use('/facebook', require('./facebook'));
router.use('/twitter', require('./twitter'));
router.use('/instagram', require('./instagram'));
And this is, for example, my auth/twitter/index.js
var router = express.Router();
router
.get('/', passport.authenticate('twitter', {
failureRedirect: '/',
session: false
}))
.get('/callback', passport.authenticate('twitter', {
failureRedirect: '/',
session: false
}), auth.setTokenCookie);
module.exports = router;
But how could I pass for example a mongodb _id to this auth/twitter/passport.js in order to pass it to the mongoose query and update an user? Something like making a POST to auth/twitter and accessing to req.user._id ? I can't figure out how to do it.
exports.setup = function (User, config) {
var passport = require('passport');
var TwitterStrategy = require('passport-twitter').Strategy;
var TwitterApi = require('twitter');
passport.use(new TwitterStrategy({
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: config.twitter.callbackURL
},
function(token, tokenSecret, profile, done) {
User.findOne({
'twitter.id_str': profile.id
}, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
user = new User({
role: 'user',
[...]
Thank you very much.
EDIT:
This is how I set my cookie:
function setTokenCookie(req, res) {
if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'});
var token = signToken(req.user._id, req.user.role);
res.cookie('token', JSON.stringify(token));
res.redirect('/');
}
and the signToken function:
function signToken(id) {
return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*24*30 });
}
Why req.user and req.session are always empty in my Strategy?
EDIT2:
I think I could use the auth.isAuthenticated() function to attach user to the request before invoking the Strategy. What I have done is this:
router
.get('/', auth.isAuthenticated(), passport.authenticate('twitter', auth.isAuthenticated, {
failureRedirect: '/',
session: false
}))
.get('/callback', auth.isAuthenticated(), passport.authenticate('twitter', {
failureRedirect: '/',
session: false
}), auth.setTokenCookie);
But now I'm having this problem:
UnauthorizedError: No Authorization header was found
My request to auth/twitter comes from a $window.location. It seems that this does not attach the user object to the request, because when I make a GET or POST using isAuthenticated() the user object is passed correctly. This is my isAuthenticated() function:
function isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if(req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id, function (err, user) {
if (err) return next(err);
if (!user) return res.send(401);
req.user = user;
next();
});
});
}
FYI I just dealed with this decoding the JWT token in my Strategies. I don't know if this is a good practice but the problem was I was not having my user attached to the request if the request was made with a $window.location.href
So in my strategies I read the cookie and decode it on the fly for searching a user in database.
Thank you.
You can set passReqToCallback to true when you define your passport strategy. It will make the current request, thus the current logged in user available to your callback function.
exports.setup = function (User, config) {
var passport = require('passport');
var TwitterStrategy = require('passport-twitter').Strategy;
var TwitterApi = require('twitter');
passport.use(new TwitterStrategy({
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL: config.twitter.callbackURL,
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
User.findOne({
'twitter.id_str': profile.id
}, function(err, user) {
if (err) return done(err);
if (!user) {
if (req.user) {
[...]
I setup passport on nodejs and have it working with mongoose for allowing users to login and create new accounts.
app.js:
var express = require('express')
, app = module.exports = express.createServer()
, passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, routes = require('./routes/index')(app) //index loads in multiple routes
, MongoDBConnection = require('./database/DatabaseConnector').MongoDBConnection;
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({ secret: 'justdoit' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
var mongoDbConnection = new MongoDBConnection();
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
//conditions....
});
});
}
));
app.get('/', function(req, res){
res.render('index', { title: "Index", user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { title: "Account", user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { title: "Login", user: req.user, message: req.flash('error') });
});
app.post('/login',
passport.authenticate('local', {
successRedirect: '/account',
failureRedirect: '/login',
failureFlash: true })
);
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
My problem is the app.js (which is where the passport code is) file is getting a bit large and I tried to move the passport sections into its own script and have the routes outside the app.js and in its own auth.js route file and then reference the routes via the app.js. It works for other routes but for passport related ones such as login it doesnt appear to fire the passport.authenicate() function.
Is there anyway I can put passport routes and functions into its own file and call it/load it from app.js?
auth.js:
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user ' + username });
}
if (user.password != password) {
return done(null, false, { message: 'Invalid password' });
}
return done(null, user);
});
});
}
));
app.get('/', function(req, res){
res.render('index', { title: "Index", user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
console.log("directing to the account page....");
res.render('account', { title: "Account", user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { title: "Login", user: req.user, message: req.flash('error') });
});
app.post('/login',
passport.authenticate('local', {
successRedirect: '/account',
failureRedirect: '/login',
failureFlash: true })
);
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
}
This is what I do. Please comment if you need more help tailoring it to your code.
First Step
Put your passport code in a separate file. e.g. pass.js. (I see you have already done that) Then, in that file, put all the code inside this:
module.exports = function(passport, LocalStrategy){
};
Remember to add to the function input anything else that you are using. In your case, besides passport and LocalStrategy, you will probably need to add mongoDbConnection as an input too.
Second Step
In your app.js, include this line. Just before "app.listen" if possible to ensure that everything has been properly defined/declared/included.
require('./pass.js')(passport, LocalStrategy);
Explanation
The "wrapper" in step one defines the chunk of code you will be including into your app. The "require" in step two is the code that actually includes it. You are basically defining the entire "pass.js" file as a function and passing it the tools it needs to carry out the code (passport, LocalStrategy etc)
In your case, you will probably need to modify my code to:
module.exports = function(passport, LocalStrategy, mongoDbConnection){
};
require('./pass.js')(passport, LocalStrategy, mongoDbConnection);
This should works. I googled about this a while ago and this appears to be the "correct" way to break up your app.js (I say this with great trepidation though :) ). Feel free to comment if you need anymore help.
This github repo also has a good example of this.
https://github.com/madhums/nodejs-express-mongoose-demo
The server.js file would be your app.js. And the /config/passport.js is the included passport setup.
For this I'll suggest to do this In app.js
require('./mypassport')(app);
And
mypassport.js
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, MongoDBConnection = require('./database/DatabaseConnector').MongoDBConnection;
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
mongoDbConnection.findUserById(id, function(err, user){
done(err, user);
});
});
passport.use(new LocalStrategy(
function(username, password, done) {
process.nextTick(function () {
mongoDbConnection.findUser(username, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: 'Unknown user ' + username });
}
if (user.password != password) {
return done(null, false, { message: 'Invalid password' });
}
return done(null, user);
});
});
}
));
}
Adding on to Legendre's answer. module.exports = function() is a way in nodejs to make a file, a variable or a certain functionality globally available to the entire application.
// anyfile.js
module.exports = function(){
//global code.
}
module.exports = function(app){
passport.serializeUser(function(user, done) {
done(null, user.id);
});
Maybe it doesnt work because u do not have a reference to a passport object ?