I am running an express server and came across a middleware problem with passport authenticate which is something like this
passport.use('google', new GoogleStratergy({
clientID: "",
clientSecret: "",
callbackURL: "/Users/auth/google/callback",
passReqToCallback : true
}, (request, accessToken, refreshToken, profile, done) => {
Users.findOne({ UserID: profile.id }, function (err, user) {
if (err){
return done(err);
}
if (!user){
var User = new Users({});
User.setPassword(Math.random().toString(36).substring(5));
var jwt = User.generateJWT();
User.save()
.then(() => {
return done(null, User, jwt);
})
.catch(err => {
return done(err);
})
}
else {
var jwt = user.generateJWT();
return done(null, user, jwt);
}
});
}
));
just now I don't know how to access the objects passed in the done function when using the middleware in another route like this.
router.get('/something', passport.authenticate('google'), (req, res, next) => {
// Now I need to access User and jwt object here passed with done()
res.send("whatever");
})
Have you checked the req object? do this
router.get('/something', passport.authenticate('google'), (req, res, next) => {
console.log(req.user)
console.log(req.jwt)
// Now I need to access User and jwt object here passed with done()
res.send("whatever");
})
Related
I'm trying to authenticate and authorize users with Passport.js using Google API. I have G Suite and all my users are part of a G Suite organization. I've set up a bunch of roles in G Suite to use with my custom app.
The authentication part is fine and works as intended. The problem is that I need to make a subsequent call to https://www.googleapis.com/admin/directory/v1/customer/customer/roleassignments with the userKey of the logged-in user to get all the assigments that this user has and I'm not sure what's the best way to do that.
This is what the relevant parts of my /login route look like now:
passport.use(
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.BASE_URL + '/login/google/callback'
},
function (accessToken, refreshToken, profile, done) {
return done(null, profile)
})
)
router.get('/', function (req, res, next) {
// Render the login button
})
router.get('/google',
passport.authenticate('google', {
scope: [
'email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly'
]
})
)
router.get('/google/callback', function (req, res, next) {
passport.authenticate('google', function (err, user, info) {
if (err) { return next(err) }
if (!user) { return res.redirect('/login?error=no_user') }
req.login(user, function (err) {
if (err) { return next(err) }
const accessToken = req.user.accessToken
// Maybe do something here?
return res.redirect('/')
})
})(req, res, next)
})
I can store the accessToken to the user session when I get it and just make a callback using the token as a bearer token and this probably works fine, but is this really the way it's supposed to work? Does Passport.js provide some kind of mechanism to make the subsequent, authenticated calls easier?
After further investigations, I believe you're supposed to modify the Strategy callback if you have additional steps to take after the initial authentication.
So to get custom User Roles set up in G Suite Admin, you could do it like this using the external request library:
import request from 'request'
passport.use(
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.BASE_URL + '/login/google/callback'
},
function (accessToken, refreshToken, profile, done) {
const url = 'https://www.googleapis.com/admin/directory/v1/customer/' +
process.env.GOOGLE_CUSTOMER_ID +
'/roleassignments' +
'?userKey=' +
profile.id
request.get(url, {
auth: {
bearer: accessToken
}
}, (error, response, body) => {
if (error) {
console.error(error)
return done(null, false, { message: 'Could not authenticate.' })
}
const items = JSON.parse(response.body).items
let role = null
const roles = items.map((value) => {
return value.roleId
})
if (roles.includes(process.env.GOOGLE_ROLE_ID_ADMIN)) {
role = 'ADMIN'
} else if (roles.includes(process.env.GOOGLE_ROLE_ID_OTHER)) {
role = 'OTHER'
} else {
return done(null, false, { message: 'No valid user role' })
}
console.log('User role is ' + role)
profile.role = role
return done(null, profile)
})
})
)
If you assign the acquired role to the profile, you can easily access it later with req.user.role.
I'm trying to implement login endpoint with passport.js. My user schema contains nickname and password. So far in app.js I have:
require('./config/passport');
app.use('/user', passport.authenticate('jwt', {session: false}), require('./routes/user/login.js'));
I require my file passport.js which have passport strategy configuration:
passport.use(new LocalStrategy({ usernameField: 'nickname' }, (nickname, password, done) => {
User.findOne({ nickname: nickname })
.then(user => {
if(!user) {
return done(null, false, { message: 'Email and/or password is not valid' });
}
bcrypt.compare(password, user.password, (err, isMatch) => {
if(err) throw err;
if(isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Email and/or password is not valid' });
}
});
});
})
);
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: keys.secretJWT
},
function (jwtPayload, cb) {
return User.findOneById(jwtPayload.id)
.then(user => {
return cb(null, user);
})
.catch(err => {
return cb(err);
});
})
);
and in app.js (first snippet) on /user I'm requirung actual endpoint which is:
router.post('/login', function (req, res, next) {
passport.authenticate('local', {session: false}, (err, user, info) => {
console.log(info);
if (err || !user) {
return res.status(400).json({
message: 'Email and/or password is not valid',
user: user
});
}
req.login(user, {session: false}, (err) => {
if (err) next(err);
//generate a signed JWT with the contents of user object and return it in the response
const token = jwt.sign(user, keys.secretJWT);
return res.json({user, token});
});
})(req, res);
});
so in the end it is invoked when calling /user/login. Weird to me is that console.log(info); never actually consoles, I can't see it in terminal in which my node app is running.
I'm passing valid nickname and password but I always get 401 Unauthorized:
What am I missing? Maybe it's because I'm not passing token? But on login I don't know the token. It should be in response of login. And then I will authenticate with token on further requests.
I think you are missing your strategy alias in passport.use() function. You use a alias jwt in your router:
passport.authenticate('jwt', {session: false})
So your passport.use() function should look like this:
passport.use('local', new LocalStrategy(...), function(payload, cb){...})
passport.use('jwt', new JWTStrategy(...), function(payload, cb){...})
Otherwise you cant match a proper strategy with an alias. Also in a login controller you have to call passport with a 'local' alias in this case.
I have code in router
router.post('/auth', function(req, res) {
oauth.auth(req, res);
});
correctly hitting
accesstokenController.auth = function(req, res) {
console.log('Here auth called');
passport.initialize(), passport.authenticate(
'local', {
session: false,
scope: []
},(req,res)), serialize, generateToken, respond
};
(req,res) added after getting a link which suggest this
I belive it should now call
passport.use(new Strategy(
function(username, password, done) {
console.log('Here pass called with ' + username + ' - ' + password);
db.authenticate(username, password, done);
}
));
But it never call and timeout occured.
If Id drectly call like this
app.post('/auth', passport.initialize(), passport.authenticate('local', { session: false,scope: [] }), serialize, generateToken, respond);
this is OK,
In my above method
accesstokenController.auth = function(req, res) {
console.log('Here auth called');
passport.initialize(), passport.authenticate(
'local', {
session: false,
scope: []
},(req,res)), serialize, generateToken, respond
};
I have just created a separate method and called it from router page, rather than calling this itslef
What I am missing
Other code
const db = {
updateOrCreate: function(user, cb) {
cb(null, user);
},
authenticate: function(username, password, cb) {
console.log('Here called');
User.findOne({ username: username,
password: password }).exec(function (err, user) {
if (err) {
cb(null,null);
}
else {
cb(null,user);
}
});
}
}
function serialize(req, res, next) {
console.log('Here pass called with ser ');
db.updateOrCreate(req.user, function(err, user) {
if (err) {
return next(err);
}
// we store information needed in token in req.user again
req.user = {
id: user.id
};
next();
});
}
function generateToken(req, res, next) {
req.token = jwt.sign({
id: req.user.id,
}, SECRET, {
expiresIn: TOKENTIME
});
next();
}
function respond(req, res) {
res.status(200).json({
user: req.user,
token: req.token
});
}
I have many link related to that but did not manage to solve this
Your strategy needs to return done() without which the Strategy doesn't know when it's completed, thus resulting in a timeout.
Difficult to say if this is the exact problem without further context.
I'm building a Node application in which the users must register or login, then when they drag and drop some elements (the front end is all working) I store on the database their action with their corresponding userId.
My understanding is that once they are registered/logged in, I can use the req.user to access their id and correctly store their actions, however it isn't working.
Here is the section of my server.js file that deals with Passport. Also, I'm using Sequelize as an ORM, but everything dealing with the database works perfect without the req.user part.
app.use(cookieParser());
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
/****** Passport functions ******/
passport.serializeUser(function (user, done) {
console.log('serialized');
done(null, user.idUser);
});
passport.deserializeUser(function (id, done) {
console.log("start of deserialize");
db.user.findOne( { where : { idUser : id } } ).success(function (user) {
console.log("deserialize");
console.log(user);
done(null, user);
}).error(function (err) {
done(err, null);
});
});
//Facebook
passport.use(new FacebookStrategy({
//Information stored on config/auth.js
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
profileFields: ['id', 'emails', 'displayName', 'name', 'gender']
}, function (accessToken, refreshToken, profile, done) {
//Using next tick to take advantage of async properties
process.nextTick(function () {
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(err) {
return done(err);
}
if(user) {
return done(null, user);
} else {
//Create the user
db.user.create({
idUser : profile.id,
token : accessToken,
nameUser : profile.displayName,
email : profile.emails[0].value,
sex : profile.gender
});
//Find the user (therefore checking if it was indeed created) and return it
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(user) {
return done(null, user);
} else {
return done(err);
}
});
}
});
});
}));
/* FACEBOOK STRATEGY */
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback//
app.get('/auth/facebook', passport.authenticate('facebook', { scope : ['email']}));
/* FACEBOOK STRATEGY */
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/' }),
function (req, res) {
// Successful authentication, redirect home.
res.redirect('../../app.html');
});
app.get('/', function (req, res) {
res.redirect('/');
});
app.get('/app', isLoggedIn, function (req, res) {
res.redirect('app.html');
});
app.post('/meal', function (req, res) {
//Testing Logs
/*console.log(req.body.foodId);
console.log(req.body.quantity);
console.log(req.body.period);
console.log(req.body);
*/
//Check whether or not this is the first food a user drops on the diet
var dietId = -1;
db.diet.findOne( { where : { userIdUser : req.user.idUser } } ).then(function (diet, err) {
if(err) {
return done(err);
}
if(diet) {
dietId = diet.idDiet;
} else {
db.diet.create( { userIdUser : req.user.idUser }).then(function (diet) {
dietId = diet.idDiet;
});
}
});
db.meal.create({
foodId : req.body.foodId,
quantity : req.body.quantity,
period : req.body.period
}).then(function (meal) {
console.log(meal.mealId);
res.json({ mealId : meal.mealId});
});
});
From what I read on the documentation for Passport, the deserializeUser function that I implemented should be called whenever I use req.user, however, with my console.logs(), I found out that serializeUser is called after logging in, therefore it is storing my session, but deserializeUser is never called! Ever.
Any idea on how to get around this? Any help is appreciated, thank you!
You need the express session middleware before calling passport.session(). Read the passportjs configuration section on documentation for more info.
Make sure to set cookieParser and express-session middlewares, before setting passport.session middleware:
const cookieParser = require('cookie-parser')
const session = require('express-session')
app.use(cookieParser());
app.use(session({ secret: 'secret' }));
app.use(passport.initialize());
app.use(passport.session());
To test if passport session is working or not, use:
console.log(req.session.passport.user)
(put in on a middleware for example)
In my case, i was using LocalStrategy and i was thinking i can protect and endpoint with simple username and password as form parameters, and i though passport will only use form parameters when it can't find user in session. but it was wrong assumption. in passport localStrategy, you should have separate endpoints for login and protected endpoint.
So Make sure you're using right middlewares for each endpoints. in my case:
wrong:
Protected endpoint:
app.get('/onlyformembers', passport.authenticate('local'), (req, res) => {
res.send({"res": "private content here!"})
})
correct :
Login:
app.post('/login', passport.authenticate('local'), (req, res) => {
res.send('ok')
})
Protected endpoint:
var auth = function (req, res, next) {
if (req.isAuthenticated())
return next();
res.status(401).json("not authenticated!");
}
app.get('/onlyformembers', auth, (req, res) => {
res.send({"res": "private content here!"})
})
I am using passportjs for authentication on my server. I am using the following code:
exports.auth = function(req, res, next){
passport.authenticate('bearer', { session: false })(req, res, next);
};
passport.use(new BearerStrategy(
function(token, done) {
User.findOne({ token: token }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false);
}
return done(null, user, { scope: 'read' });
});
}
));
Is there a way to access the req object in passport.use? This was I can get the user ip address and check for eventual attacks.
The comments in the example suggest that you can pass an object { "passReqToCallback": true } to make the req callback available in the callback function. Which can be accessed as
function(req, token, done){//rest of the function body}
So initialize passport.use as
passport.use(new BearerStrategy({ "passReqToCallback": true },
function(req, token, done) {
});
and you should have req in the callback.