Linkedin Passport Oauth failureRedirect - node.js

Good afternoon,
I'm working in a node application. Concretely I'm working with "passport-linkedin-oauth2".
There is my code.
linkedin/index.js
'use strict';
var express = require('express');
var passport = require('passport');
var auth = require('../auth.service');
var router = express.Router();
router
.get('/', passport.authenticate('linkedin', {
state: 'comienzo'
}),
function(req, res){
// The request will be redirected to Linkedin for authentication, so this
// function will not be called.
})
.get('/callback', passport.authenticate('linkedin', {
failureFlash : true,
failureRedirect: '/login'
}), auth.setTokenCookie);
module.exports = router;
linkedin/passport.js
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
var models = require('../../api');
exports.setup = function (User, config) {
passport.use(new LinkedInStrategy({
clientID: config.linkedin.clientID,
clientSecret: config.linkedin.clientSecret,
callbackURL: config.linkedin.callbackURL,
scope: [ 'r_basicprofile', 'r_emailaddress'],
state: true
},
function(accessToken, refreshToken, profile, done) {
process.nextTick(function () {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
models.User.findOrCreate({
where: {
linkedin: profile.id
},
defaults: {
name: profile.displayName,
linkedin: profile.id,
mail: profile.emails[0].value,
password: 'xxxxxxx',
role: 'admin', provider: 'linkedin',
activo: true
}
}).spread(function (user, created) {
console.log("x: " +user.values);
return done(null, user)
}).catch(function (err) {
console.log('Error occured', err);
return done(err);
});
}
));
};
The problem I'm facing is that I'm pretty sure that LinkedIn is logging properly.
In my app when i press login button it redirect me to LinkedIn webpage, I fill the information and then my server receives this answer
GET /auth/linkedin/callback?code=AQTfvipehBLAXsvmTIl1j3ISYCzF03F-EilhiLlfSJNqiwfQsyHeslLONOWY12Br-0dfV1pgkSSpCKlmtpiMVUCufJlatEBswWqfPe6iahoRF8IHIhw&state=comienzo 302 4ms - 68b
I think that this means that it is ok because I get the state that I have sent to LinkedIn API before and the code.
Anyway, every time I login always redirect me to Login page which is failureRedirect: '/login' ( I have tested that if I change this route, the app redirect me where this attribute point)
Also I have checked that it never executes the code that search in the db for the linkedin user.

Remove the state property on the handler or at the strategy instantiation, i'm not sure why but this solves the issue.
exports.setup = function (User, config) {
passport.use(new LinkedInStrategy({
clientID: config.linkedin.clientID,
clientSecret: config.linkedin.clientSecret,
callbackURL: config.linkedin.callbackURL,
scope: [ 'r_basicprofile', 'r_emailaddress'],
state: true // <-- Remove state from here
})
}
and this code
router
.get('/', passport.authenticate('linkedin', {
state: 'comienzo' // <-- Or Remove state from here
}),
You can just set it the state on one of this places but not both, so remove one of them

Related

Oath facebook error: “URL blocked: This redirect failed because...“ and cannot get

I saw all the possible answers online but I just cannot get this to work and don't understand why. First problem I came across is the usual: "URL Blocked: This redirect failed because the redirect URI is not whitelisted in the app's Client OAuth Settings". After a few hours trial and error, trying every possible way (http, https, www, and without) and I managed the app to save the FacebookId in mongoDB (although didn`t prompt me to the Facebook login page), but right after now I have Cannot GET /auth/facebook message.. Now I know I have an issue already in mondoDB because at this point no multiple account can be saved without and email address as username would be NULL and only one allowed, but after wiping the DB I can sign in with Google oath without and issue so, it seems like I have a problem setting up
Facebook and i just don`t know what im doing wrong. Thank you in advanced!
facebooksettings
facebooksettings2
the site url set to: https://app-secret.herokuapp.com/
app.js
...
//use session package with some setup config//
app.use(session({
secret: 'My little secret.',
resave: false,
saveUninitialized: false,
}))
//initalize passport packadge and for also to deal with the session//
app.use(passport.initialize());
app.use(passport.session());
mongoose.connect("mongodb+srv://andras:MYPASSWORD#cluster0.zfr0d.mongodb.net/userDB", {
useNewUrlParser: true,
useUnifiedTopology: true
});
//schema in order to have a plugin it has to be a mongoose schema//
const userSchema = new mongoose.Schema({
email: String,
password: String,
googleId: String,
facebookId: String,
secret: Array
});
//adding plugins to schema//
userSchema.plugin(passportLocalMongoose);
userSchema.plugin(findOrCreate);
const User = new mongoose.model("User", userSchema);
//configuring passport, serialize=create and deserialize=able to crack open cookie//
passport.use(User.createStrategy());
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new GoogleStrategy({
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "https://app-secret.herokuapp.com/auth/google/secrets",
userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
},
function(accessToken, refreshToken, profile, cb) {
// console.log(profile);
User.findOrCreate({
googleId: profile.id
}, function(err, user) {
return cb(err, user);
});
}
));
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: "https://app-secret.herokuapp.com/auth/facebook"
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({
facebookId: profile.id
}, function(err, user) {
return cb(err, user);
});
}
));
app.get("/", function(req, res) {
res.render("home")
});
app.get("/auth/google",
passport.authenticate('google', {
scope: ["profile"]
})
);
app.get("/auth/google/secrets",
passport.authenticate('google', {
failureRedirect: "/login"
}),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/secrets');
});
app.get('/auth/facebook',
passport.authenticate('facebook'));
app.get('/auth/facebook/secrets',
passport.authenticate('facebook', {
failureRedirect: '/login'
}),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/secrets');
});
So sorry to spam stackoverflow with my rookie misery. I realised where i went wrong, i thought the facebook API is the one creating me headache and not realising that the facebook callback in the app.js missing the correct path.. instead of: "app-secret.herokuapp.com/auth/facebook" it should be: "app-secret.herokuapp.com/auth/facebook/secrets"

how to use passport-google-oauth20 twice in the same app

I'm trying to use the passport-google-oauth20 twice in my node app but it's not working. Still learning passport authentication.
So here's the problem:
i made a button for the user to login using google(/auth.google) and a button for a user to register using google(/users/google). I want it so that if the user is already registered on my app using google and he goes on a presses "register using google" button, he should be redirected back to the register page(auth fails). In my case when i add the strategy once and add a login button only(without register), auth works and users is redirected to dashboard. However, when i add the strategy again in passport.js and the 'already registered user" tries to login using google, he gets redirected to register page(where the register using google button is found) instead of dashboard. I know there's no need to do such thing and no need to use the same strat again for registeration as it works the same for logging in. just wondering if it's possible to work.
Here's my code:
Passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const myClientId = require("./keys").clientID;
const myClientSecret = require("./keys").clientSecret;
// get the user schema
const User = require("../models/User");
// passport google authentication
module.exports = function (passport) {
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/auth/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, user);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
// using same strategy again but different callback URL
passport.use(
new GoogleStrategy(
{
clientID: myClientId,
clientSecret: myClientSecret,
callbackURL: "/users/google/callback",
},
(accessToken, refreshToken, profile, done) => {
const newUser = new User({
googleId: profile.id,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
image: profile.photos[0].value,
});
User.findOne({ googleId: profile.id })
.then((user) => {
if (user) {
done(null, false);
} else {
newUser.save().then((userd) => {
done(null, userd);
});
}
})
.catch((err) => console.log(err));
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
};
app.js
/* initalized passport, session and other stuff here but just showing you the routes and the passport
function in app.js */
require("./config/passport")(passport);
app.use("/auth", require("./routes/auth"));
app.use("/users", require("./routes/users"));
auth.js
const express = require("express");
const passport = require("passport");
const router = express.Router();
//? register and login handle
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
//? google auth callback
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/",
})
);
module.exports = router;
users.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
const { ensureAuthenticated, forwardAuth } = require("../config/checkAuth");
//? register page
router.get("/register", forwardAuth, (req, res) => {
res.render("register");
});
//? login page
router.get("/login", forwardAuth, (req, res) => {
res.render("login");
});
//? logout handle
router.get("/logout", (req, res) => {
req.logOut();
res.redirect("/");
});
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/dashboard",
failureRedirect: "/users/register",
})
);
module.exports = router;
So if the users logs in on /auth/google ...he gets redirected to the register page(/users/register)! Can't know why
I solved my problem.
the solution was actually simple. Just using the same passport.js file.
in passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
module.exports = function (passport) {
passport.use("firstUse", new GoogleStrategy(....))
passport.use("secondUse", new GoogleStrategy(...))
}
so the thing is that you can register the strategy with any name you want and then when you want to handle the authorization middleware in the routes you just do this:
in users and auth.js
app.get("/auth/google/callback", passport.authenticate("firstUse"));
app.get("/users/google/callback", passport.authenticate("secondUse"));
just add the name that you registered with. I thought that it is necessary to only use passport.authenticate("google") and not any name else than "google".

Node.js AUTHORIZE endpoints with Oauth2-Google passport

I'm trying to develop a simple API in node and hiding it behind a google authentification layer, for that I'm using passport-google-oauth2 to enforce a google authentification to do some requests.
....
var GoogleStrategy = require('passport-google-oauth2').Strategy;
var passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new GoogleStrategy({
clientID: CLIENT_ID,
clientSecret: CLIENT_SECRET,
callbackURL: "/oauth2callback",
passReqToCallback : true
},
function(request, accessToken, refreshToken, profile, done) {
console.log("passportUse access Profile ", accessToken);
done(null, accessToken);
}
));
app.get('/oauth2callback',
passport.authenticate( 'google', {
successRedirect: '/notesSuccess',
failureRedirect: '/notesNotSuccess'
}));
app.get('/notes',
passport.authenticate('google', { scope:
[ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/drive'] }
), function(req, res) {
//res.json({notes: "This is your notebook. Edit this to start saving your notes!"})
})
app.get('/notesSuccess', function(req, res) {
res.json({notes: "This is your notebook. Edit this to start saving your notes!"})
})
app.get('/notesNotSuccess', function(req, res) {
res.json({notes: "AUTHENTICATION FAIL"})
})
app.get('/notes/API1', function(req, res) {
res.json({notes: "API1111111"})
})
....
The code is working more or less, at passport.use I do get all the google plus profile information and a token, so I assume that part is working.
The issue is that when I use the browser to visit localhost:8080/notes, I get the results as expected (/notesSuccess) when I login, if I'm in a private window I can see the google login page, working well so far.
My problem is if I do a query with postman using the token I was given or a new token with the same scopes I get only the login page (as if I had no Authentification header). If I query notes/API1 (no Oauth there) it works properly
Any ideas why the headers are not authentifing the query?

Pass user info in routes from Passport strategies

I am trying to authenticate user using passportjs using express.
Passport.js looks like this.
var USER_INFO = {};
var FB_CALLBACK = 'http://localhost:3000/auth/facebook/callback';
module.exports = function(passport) {
passport.use(new FacebookStrategy({
clientID: FB_CLIENT_ID,
clientSecret: FB_CLIENT_SECRET,
callbackURL: FB_CALLBACK
},
function(accessToken, refreshToken, profile, done) {
process.nextTick(function() {
USER_INFO.id = profile.id;
});
}));
}
var express = require('express'); // call express
var app = express(); // define our app using express
var router = express.Router(); // get an instance of the express Route
var passport = require('passport');
USER_INFO = {};
require('./config/passport')(passport);
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
successRedirect : '/fb',
failureRedirect : '/error'
}));
app.get('/fb', function (req, res) {
res.json(USER_INFO);
});
I want all the information extracted in res.json(user_info). But it is coming as empty. What I am missing here. What is the best method to save user basic info of user to keep him logged in.
Firstly, you should not store USER_INFO = {} outside the scope of your current request. If two separate users make a request then they'll get the same object.
You should at least store them in a way you can retrieve them separately
var USERS = {};
...
module.exports...
passport.use...
...
function(accessToken, refreshToken, profile, done) {
USERS[profile.id] = profile;
done(null, profile);
}));
Now if two separate users make a request they'll have their info separately stored within USERS
{
1234: {id: 1234, name: FOO},
6789: {id: 6789, name: BAR},
}
And done(null, profile) would serialize that user. If you haven't defined your serialize/deserialize functions you should do so like this:
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
var user = USERS[id];
done(null, user);
});
Now your users will be available within their respective request contexts as req.user
So you just need to do:
app.get('/fb', function (req, res) {
res.json(req.user);
});
You forgot call done() in process.nextTick().
var FB_CALLBACK = 'http://localhost:3000/auth/facebook/callback';
module.exports = function(passport) {
passport.use(new FacebookStrategy({
clientID: FB_CLIENT_ID,
clientSecret: FB_CLIENT_SECRET,
callbackURL: FB_CALLBACK
},
function(accessToken, refreshToken, profile, done) {
process.nextTick(function() {
var USER_INFO = {};
USER_INFO.id = profile.id;
done(USER_INFO)
});
}));
}
You can pass any object to done(), it will become req.user later in your route. In your case, the USER_INFO which you want to response is req.user
app.get('/fb', function (req, res) {
res.json(req.user);
});

Saving multiple passportjs providers into same user document in mongodb

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

Resources