OAuth Flow redirecting to "Unauthorized" page - node.js

In my web app, I use Spotify's OAuth flow to authenticate a user. I store the user's information in my database like this.
passport.use(
new SpotifyStrategy({
clientID,
clientSecret,
callbackURL,
proxy: true
}, async (accessToken, refreshToken, profile, done) => {
const user = await User.findOne({ spotifyId: profile.id });
if (user) {
user.accessToken = accessToken;
user.refreshToken = refreshToken;
const replace = await user.save();
return done(null, replace);
}
const newUser = await new User({spotifyId: profile.id, accessToken, refreshToken}).save();
return done(null, user);
})
In other words, if my user already exists in my database, then all I want to do is update the access/refresh token. If the user does not exist, then I want to create a new user document.
My problem occurs when a first-time user logs into my web app. I noticed that because this first-time user is not in my database, the redirect-url will go to a page that simply says unauthorized. However, this login-attempt will put the user's information into the database. So, even though the user initially went to the unauthorized page, if he or she tries to login again, it will work successfully. Likewise, any user that has already visited my website before will have no trouble logging in again.
So, if a user's information is not already in the database, they will be redirected to the unauthorized page when they go to the route '/auth/spotify/callback', and if they are already in, then the website will work as normal.
I can't figure out why this is happening. I initially thought that there might be a page that requires the authorization token, but because my user might not have one yet, it says unauthorized. But, I did some testing and found out that this is not the reason. I also tried redirecting to a page where the authorization token is not needed, and it still messes up. I also thought maybe I spelled the auth callback route incorrectly in the app, but I'm sure I spelled it correctly.
Here are my auth routes
app.get('/auth/spotify',
passport.authenticate('spotify', {
scope: ['playlist-read-collaborative', 'playlist-read-private',
'user-read-playback-state', 'user-modify-playback-state',
'user-read-currently-playing', 'streaming']
}),
(req, res) => {
}
)
app.get('/auth/spotify/callback',
passport.authenticate('spotify'),
(req, res) => {
res.redirect('/');
});
If anyone has an idea of how to fix this bug, I will greatly appreciate it. Thank you!

I'm an idiot. At the bottom, I should be returning newUser, not user.

Related

Handling authentication in Nodejs with passport-facebook-token, request coming from frontend Facebook SDK

I am working on a Unity App. For login, there are two methods, one using Email and another using Facebook. In case of login separately, I do not have any problem. Registration and Login with Email works perfectly. And Login with Facebook works perfectly as well. Here's the workflow, I created just to make you clear.
tl;dr [read update]
There's another schema for account, which is used for login.
var Account = new Schema({
email: String,
password: String,
facebookId: String
});
Things to know about the backend API.
Passport is used for Authentication
Successful login returns email and token to the client through API.
On client, token is most to play game and use the overall features.
As I said, I have already covered the part when if a client registers and login using email, then client can use the app. But my confusion is handling the logins with Facebook. Facebook SDK is already integrated with the Unity App, and Login is success.
Now, how can I use the Facebook login information that is generated by the Facebook SDK onto my back end, so that I can authorize the user throughout the system, as done in email login.
Going through other questions in SO and Google, I came across passport-facebook-token, I also tried using the plugin but could not came up with the logic and flow for handling the data from SDK into the Nodejs API. Can someone me help understand how it is done?
Update 1: Using passport-facebook-token
Strategy on index.js
passport.use(new FacebookTokenStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET
}, function(accessToken, refreshToken, profile, done) {
Account.findOrCreate({facebookId: profile.id}, function (error, user) {
return done(error, user);
});
}
));
Controller API
api.post('/auth/facebook/token',
passport.authenticate('facebook-token'),
function (req, res) {
console.log(req.user);
// do something with req.user
res.sendStatus(req.user? 200 : 401);
}
);
Now, there is no error shown, but the data is not inserted into Account Schema, I have this findOrCreate() function in Model.
Account.statics.findOrCreate = function findOrCreate(profile, cb){
var userObj = new this();
this.findOne({facebookId : profile.id},function(err,result){
if(!result){
userObj.facebookId = profile.id;
//....
userObj.save(cb);
}else{
cb(err,result);
}
});
};
you can use facebook-passport for that, you can check the documentation here: https://github.com/jaredhanson/passport-facebook but basically, after you have already set up your developer account and got your keys from the developer site of facebook you can implement a FacebookStrategy object like following where you have to specify your credential and also a callback that in the documentation example is an http request to another resource of an express server where you can then save the data to mongo
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, cb) {
User.findOrCreate({ facebookId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));

Setting Up Postman for API Testing When Using Passport Authorization

I am a bit confused while trying to get Postman to work when testing the API of my application. Namely, I am using Passport authentication; however, I do not know which type it defaults to or uses in my code. How can I figure this out and which type should I choose in Postman?
Here is the relevant Passport code:
var login = require('./login');
var signup = require('./signup');
var User = require('../models/user');
module.exports = function(passport, path, nodemailer, sesTransport, EmailTemplate, templateDir, template){
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function(user, done) {
//console.log('serializing user: ');console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
//console.log('deserializing user:',user);
done(err, user);
});
});
// Setting up Passport Strategies for Login and SignUp/Registration
login(passport);
signup(passport, path, nodemailer, sesTransport, EmailTemplate, templateDir, template);
}
Lastly, pretty much all of my API points only work when the user is logged in. How can I emulate the same behavior in Postman by saving the authorization credentials?
Edit:
Perhaps this code is relevant as well:
module.exports = function(passport){
passport.use('login', new LocalStrategy({
passReqToCallback : true,
usernameField: 'email',
passwordField: 'password'
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({ 'email' : username },
function(err, user) {
// In case of any error, return using the done method
if (err)
return done(err);
// Username does not exist, log the error and redirect back
if (!user){
console.log('User Not Found with username '+username);
return done(null, false, req.flash('message', 'User Not found.'));
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false, req.flash('message', 'Invalid Password')); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}
);
})
);
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
}
}
I don't have a code that runs local auth strategy but I think the following postman setup should work for you.
To request for an access token; assuming your endpoint is auth/local.
open up Postman
create a POST request
under authorization tab set "No Auth"
under body tab -> click on x-www-form-urlencoded
add a key named email and enter the user email
add a key named password and enter the associated secret for the email
See token request screenshot below:
The response will come back with an access_token.
To use the access_token simply create a HTTP request and in the HEADER tab, add the key Authorization followed by a value of "Bearer
See use token request screenshot:
I use this and it works fine in postman.
After getting response of access token
under the Authorization tab. select "Bearer Token" from "Type" drop-down. and Token with field will appear on right. enter the access token.
This works fine with Laravel REST APIs.
Check Screen Shot
Postman Auth Token passing
What I did is. First send the login request thru postman. If you look in the response you should see a cookies tab. Copy the value of the cookie
Postman Picture
When you want to check the "protected" route in the headers you need to choose cookie and in value paste the value you have copied before.
Cookie Header
when we use passport js it stores the user information using sessions, there is a bug in express-session. so by using cookie-session it is solved ( for me ) because it gives a header parameter cookie, which we can use in postman for testing.
when we are in the browser it automatically sets the cookies in the header but for the postman, we have manually do it.
we will get the cookie info form req in express ex:
which we can use in postman like :

SailsJS Linkedin OAuth 2.0 Login Flow Issues

I'm trying to use this library to authenticate using Linkedin:
https://github.com/auth0/passport-linkedin-oauth2
No Linkedin Login Prompt
I have configured my Passport Linkedin Strategy like so:
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
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 LinkedInStrategy({
clientID: 'LINKEDIN_API_KEY',
clientSecret: 'LINKEDIN_API_SECRET',
callbackURL: 'http://localhost:1337/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_basicprofile'],
state: true
}, function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
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);
});
}));
My AuthController.js looks like this:
var passport = require('passport');
module.exports = {
login: function(req, res) {
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
},
callback: function(req, res) {
// ------------------------------------------------------------------------
// after user authenticated, we get the user's email from
// Linkedin's JSON response and save it against the matching  
// email address in the User model
// ------------------------------------------------------------------------
console.log(res);
},
logout: function(req, res) {
req.logout();
res.send('logout successful');
}
};
From the linkedin oauth library, I expect the call to:
passport.authenticate('linkedin', function...);
In my AuthController's login action, to redirect the user to Linkedin's login prompt page but what I am actually seeing is my browser just keeps on loading, loading, loading and never stops.
Am I doing something wrong ?
Some questions I am not sure of:
Does Linkedin expect my server to be running on HTTPS before it lets this whole thing starts working ?
Is there some special configurations that I need to do in my Linkedin developers app setting ? (I've enabled all the correct Javascript SDK URLs)
Callback Error
OK, so continuing on, my next problem appears to be here:
return done(null, profile);
^
TypeError: object is not a function
My code is following the npm module instruction here: https://www.npmjs.com/package/passport-linkedin-oauth2
Maybe SailsJS has another way of writing it yet again....
Authentication Always Fails
After fixing the callback error as mentioned in my solution below, I decided to keep moving on and see how it goes despite the Linkedin documentation isn't quite matching 100% to what I expect from the NPM library.
My next problem is my authenticated.js policy appears to always fail.
My code is below:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
No Login Prompt Solution
sigh
I think I'm beginning to grasp some of the difference between SailsJS and pure ExpressJS codes.
The problem appears that I was missing this piece of code at the end of my passport.authenticate() method:
(req, res)
I picked it up after looking this tutorial again: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
So now, the final authenticate method should look like:
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
})(req, res); // <--- notice this extra (req, res) code here
Which matches the Passportjs documentation:
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
In a way....if you know what I mean... :D
Now I got my Linkedin login prompt as expected.
Finally!
Callback Error Solution
OK.....I'm not sure if this is completes the login process...but....
I noticed I had an extra line:
passReqToCallback: true
Taken from this page here:
https://github.com/auth0/passport-linkedin-oauth2/issues/29
I removed that and I got a different error message.
I've also changed my callback code to look like:
passport.authenticate('linkedin', function(err, user, info) {
res.json(200, {
user: user
});
})(req, res);
and I got my user JSON which appears to be my Linkedin user profile info:
{
user: {
provider: "linkedin",
...
}
}
But that's...contradicting the Linkedin documentation...I don't see any access_token or expire_in properties which I was expecting to see in step 3 of the Linkedin OAuth 2.0 documentation (https://developer.linkedin.com/docs/oauth2)...
So...supposedly...I should take this user object and create/update against an existing user object ?
Authentication Always Fails Solution
OK, so few more days, I added extra code to generate a User entity if one isn't found in my database, otherwise just return the found user.
The was one last problem, in my policies folder, I have a authenticated.js and it looked like this:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Being new to all this web development stuff, I thought:
req.authenticated; // should call match name of the file ?
was correct but I was following this tutorial:
http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
and he named his file: isAuthenticated.js I figured it's just a name....but I was wrong :D
Turns out, the correct code was:
req.isAuthenticated()
So in full, the correct code becomes:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.isAuthenticated()) { // alright, that's more like it!
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Perhaps isAuthenticated is a Passportjs function and not just a name like I initially thought.
My further research shows this page which suggests so to me:
Problems getting Passport.js to authenticate user
Maybe req.authenticated can only be used for HTML email-password login form as suggested in above Stackoverflow post and req.isAuthenticated() is for OAuth stuff.
Anyhow, I still don't know if this is the right path but so far, I got authentication in my application now and I can access protected resources. Not sure how long I'll be logged in for, maybe I still need to build the refresh token thingo every 15 minute like the Linkedin documentation stated ?
Hope this helps other fellow Sailsjs users who are facing the same problem :)
Does Linkedin expect my server to be running on HTTPS before it lets
this whole thing starts working ?
No. The API works just as well on a local http setup.
Is there some special configurations that I need to do in my Linkedin
developers app setting ? (I've enabled all the correct Javascript SDK
URLs)
No, your setup is fine.
The browser keeps loading because after the authentication LinkedIn redirects to your callback action which isn't handling the response stream.
You need to handle the response in the callback action. Something like this will do:
callback: function(req, res) {
passport.authenticate('linkedin', function(err, user){
// handle error
// do something with the user (register/login)
return res.redirect('/home');
});
}
I'd highly recommend using sails-generate-auth for maintaining third-party logins. Very easy to setup and configure. All you need to do is serve the access tokens and secrets for the different strategies (either through config/passport.js or, preferably, through config/local.js). Will spare you a lot of redundant code.

how to use access token to authenticate user after login through twitter in node.js passport

I need to build a token based authentication on my node.js app , that the user can use the his facebook or twitter credential to login my app, and use access token to get to resource. this post is suggesting to once authenticated through facebook or twitter or other, use access token on every request, and Session is NOT needed at all
For example
GET /api/v1/somefunction?token='abcedf'
The client gets the access token from the response.
The client calls some server api with the token argument.
so the following code, is to authorize user through twitter, if my app doesn't find my user information, then store user information into the database.
passport.use(new TwitterStrategy({
consumerKey: config.twitter.clientID,
consumerSecret: config.twitter.clientSecret,
callbackURL: config.twitter.callbackURL
},
function(token, tokenSecret, profile, done) {
console.log('TwitterStrategy /auth/twitter.............',profile.id, profile.displayName, profile.username, profile.emails[0], profile._json.avatar_url);
userModel.findUserByQuery({ 'social.twitter.id': profile.id }, function (err, user) {
if (!user) {
console.log('twitter user not found'.red);
userModel.createNewUser( { username:profile.username,
email:profile.emails[0].value,
img:profile._json.avatar_url,
fullname:profile.displayName,
password:profile._json.avatar_url,
social:{twitter:{id:profile.id,avatar:profile._json.avatar_url, name:profile.username,token:accessToken} }},
function(err,data){
if(err) return done(err);
else if(!data) return done(null, false, { message: 'can not create your profile in database' });
else {
console.log('save the new twitter user into database'.green, data._id);
return done(err, user);
}
})
} else {
console.log('twitter user found'.green);
return done(err, user);
}
})
}
))
However, I have two questions,
1. how to send the access token to the client for the following requests
in the code, after authenticated from twitter, I get the access token and send this token to the client on the browser, since the token is embedded in the url parameter, I tried the code
res.redirect ('/users/profile ? token = blabla '), but in the client browser, the url is still shown as '/users/profile' rather than '/users/profile ? token=blabla'
2. once authenticated from twitter, the following request with token is going through my app locally( which I store the token in database, and compare the following token to verify) or still to twitter API to authenticate?
if in the first situation, so I should store the token into the database, in order to compare the following request in token in the following requests to my app? is that right
I'm also trying hard to get this, and just found this rather relevant answer: Call Bitbucket REST API using 2leg oauth token
I just can't get how to do this with passport? Particularly, how to get ouath instance from passport authenticated session, to do oauth.get(...), as described here: https://github.com/ciaranj/node-oauth
UPDATE: Jared (author of passportjs) has explained this is wrong approach in the google groups thread below, and recommends to use https://github.com/mikeal/request.
This is how it works for me:
var oauth = {
consumer_key: process.env.BB_CONSUMER_KEY,
consumer_secret: process.env.BB_CONSUMER_SECRET
};
passport.use(new BitbucketStrategy({
...
function(token, tokenSecret, profile, done) {
oauth = extend(oauth, {
token: token,
token_secret: tokenSecret
});
}
Note, tokens above may need be persisted per user in clustered environment.
Later, to access api, do:
request.get({url: 'protected end point', oauth: oauth})
Hope it will help someone!

Everyauth and accessing the (twitter) oauth data from within findUserById

I am trying to use everyauth within express to authenticate users to a website that allows adding text commentary to images. I am currently using the twitter oauth api to authenticate my users.
Now I'm trying to established a custom user database that allows me to keep multiple ways of logging in connected to a single entry in a CouchDB. I am running into trouble when i want to add a new user to the DB when he initially connects via twitter. The code of my findUserById reads as following:
everyauth.everymodule.findUserById (userId, callback) ->
users.findById userId, (err,res) ->
if res.id
# id returned, so there is an entry in the DB
callback null, res
else
# no entry in the DB, add a new one
users.saveById JSON.stringify(userId), {"name":"test"}, (saveErr, saveRes) ->
callback null, saveRes
everyauth.twitter
.consumerKey(config.twitterConsumerKey)
.consumerSecret(config.twitterConsumerSecret)
.findOrCreateUser((session, token, secret, user) ->
promise = #.Promise().fulfill user
).redirectPath '/'
findById and saveById are methods of a cradle object that query the DB, userId is provided by everyauth, as it seems.
My problem lies in the {"name":"test"} part. This is the location where I want to add the data from the req.session.auth.twitter object to the DB. How can I access those values from within the findUserByIdfunction?
Is that even possible? Is there a different approach? I am looking at the findOrCreateUser function, but I have no idea how to change that without crashing my app - i do not yet grasp the concept of the Promise.
I can't provide any guidance on how to solve your issue using everyauth. However, have you considered using Passport for authentication?
I'm the developer of Passport, so feel free to ask me any questions. I wrote it after trying out everyauth and being similarly frustrated by the flow control offered, including promises.
Passport has a simple mechanism for handling sign in using Twitter (or any other OAuth provider), using the passport-twitter module:
passport.use(new TwitterStrategy({
consumerKey: TWITTER_CONSUMER_KEY,
consumerSecret: TWITTER_CONSUMER_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/twitter/callback"
},
function(token, tokenSecret, profile, done) {
User.findOrCreate({ twitterId: profile.id }, function (err, user) {
return done(err, user);
});
}
));
Within the verify callback (as its known), you have complete access to the token and tokenSecret issued by Twitter, as well as the full Twitter profile. You can use that information to find or create a user in your database.
Triggering sign in is as simple as adding a couple routes to your app:
app.get('/auth/twitter', passport.authenticate('twitter'));
app.get('/auth/twitter/callback',
passport.authenticate('twitter', { successRedirect: '/',
failureRedirect: '/login' }));
After fiddling around further, I have found a solution to the problem i posed. To access the full user data that is transmitted with the OAuth request with respect to the Promise-structure of everyauth, the everyauth.twitter call should look like this:
everyauth.twitter
.consumerKey(config.twitterConsumerKey)
.consumerSecret(config.twitterConsumerSecret)
.findOrCreateUser((session, token, secret, user) ->
users.findByTwitterId user.id, (err,res) ->
if res.id
user=res.value
else
newUser = {id:user.id,name:user.name,twitter:user}
user = newUser
users.saveById JSON.stringify(user.id), user, (saveErr, saveRes) ->
promise = #.Promise().fulfill user
).redirectPath '/'
The findByTwitterId call is a function that queries the CouchDB for a view looking up documents that contain a twitter.id field.
The req.user object is populated by this:
everyauth.everymodule.findUserById (userId, callback) ->
users.findByTwitterId userId, (err,res) ->
if res.id
callback null, res.value
else
callback null, null

Resources