Facebook Login is returning 'Undefined' Fields in user profile and it doesn't return email. MEANJs + Passport-facebook - facebook

I am using Meanjs.org boilerplate and Facebook Signup returns me to the Signup page.
Following are the steps that I have taken so far.
1) Setting up the Facebook App Site URL
http://localhost:3000/
and the callback URI of OAuth
http://localhost:3000/auth/facebook/callback
2) Placing the APP_ID and APP_Secret in as Client_ID and Client_Secret
facebook: {
clientID: process.env.FACEBOOK_ID || '*****',
clientSecret: process.env.FACEBOOK_SECRET || '*****',
callbackURL: 'http://localhost:3000/auth/facebook/callback',
profileFields: ['id','emails', 'first_name', 'last_name', 'displayName', 'link', 'about_me', 'photos' ]
},
3) Code is as follows
--Routes
// Setting the facebook oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {
scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));
-- The oauthCallback function,
exports.oauthCallback = function(strategy) {
return function(req, res, next) {
passport.authenticate(strategy, function(err, user, redirectURL) {
if (err || !user) {
console.log('1' + err);
//console.log(user);
return res.redirect('/#!/signin');
}
req.login(user, function(err) {
if (err) {
console.log('2' + err);
return res.redirect('/#!/signin');
}
return res.redirect(redirectURL || '/');
});
})(req, res, next);
};
};
-- Passport-Facebook Strategy
module.exports = function() {
// Use facebook strategy
passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
callbackURL: config.facebook.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
console.log('facebook Strategy Started');
// Set the provider data and include tokens
var providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// console.log(JSON.stringify(profile));
console.log(profile);
// console.log(JSON.stringify(profile.name.givenName));
// Create the user OAuth profile
var providerUserProfile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
displayName: profile.displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'facebook',
providerIdentifierField: 'id',
providerData: providerData
};
//console.log('provider' + providerUserProfile);
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};
4) Debugging
Logging err under oauthCallback function returns the following,
1TypeError: Cannot read property '0' of undefined
What Facebook returns as profile in Passport-Facebook module is as follows,
{ id: 'Id_of_the_person',
username: undefined,
displayName: 'Full_name_of_person',
name:
{ familyName: undefined,
givenName: undefined,
middleName: undefined },
gender: undefined,
profileUrl: undefined,
provider: 'facebook',
_raw: '{"name":"Full_name_of_person","id":"Id_of_the_person"}',
_json:
{ name: 'Id_of_the_person',
id: 'Id_of_the_person',
accessToken: 'access_token_value',
refreshToken: undefined } }
Can anyone be kind to guide me about getting the correct user profile from Facebook including user email?
Thank you so much.

I have my profile fields set to the following
profileFields: ['email','id', 'first_name', 'gender', 'last_name', 'picture']
Even though you set email it might return emails if the user has multiple emails. So you need to check if email was returned profile.email or profile.emails[0].value. You must also check if it is undefined, because there is people that register with facebook that never verify their email account and there is people that sign up with a phone number, in both those cases their emails will always be undefined.
you want to check that any required fields have values.
var email = profile.email || profile.emails[0].value;
if (! email){
console.log('this user has no email in his FB');
var err = {message:'this user is missing an email'};
return done(err);
}
now i can do this if they have an email
newUser.facebook.email = email;
if they don't have an email you can set a session for profile and send them to a page that asks them to enter an email.
It sounds like a pain, but you can never trust information from a third party api to have values.
Most of the passport examples I've seen online are wrong. They all assume an email is present.

First, profileFields field does not obey to Portable Contacts convention - and you can find the convention for passportjs here.
Second, in your example, after removing removed 'about_me', the Facebook signup returns no error. Before removing 'about_me', I had a different error: Tried accessing nonexisting field (about_me) on node type (User)
If the error persist, see this serie of 5 tutorials which helps me when I was doing the sign up page to authenticate with social networks accounts.

Related

Use Firebase Cloud Function for Create User(Remove Automatically Login When register user)

When user register from client side(mobile app) , user automatically login app i dont want auto login so, I did some research, I had to use a firebase cloud function to solve this.
But I get a few errors when calling the function , how can i fix these errors
First error :
Access to XMLHttpRequest at 'https://***.cloudfunctions.net/createUser' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
second error :
zone-evergreen.js:2845 POST https://****.cloudfunctions.net/createUser net::ERR_FAILED
Third Error
core.js:4197 ERROR HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "https://***.cloudfunctions.net/createUser", ok: false, …}
Firebase Console log
2:02:23.363 ÖS
createUser
Function execution started
2:02:23.390 ÖS
createUser
Function execution took 28 ms, finished with status: 'crash'
cloud function :
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin')
admin.initializeApp();
exports.createUser = functions.https.onRequest((request, response) => {
return cors(req, res, () => {
if (request.method !== "POST") {
response.status(405).send("Method Not Allowed");
} else {
let body = request.body;
const email = body.email;
const password = body.password;
admin.auth().createUser({
email: email,
emailVerified: false,
password: password,
})
.then((userRecord) => {
return response.status(200).send("Successfully created new user: " +userRecord.uid);
})
.catch((error) => {
return response.status(400).send("Failed to create user: " + error);
});
}
})
});
client side :
signUp(email,password){
let body = {
email : email,
password : password
}
this.http.post(
'https://****.cloudfunctions.net/createUser',
body
).subscribe(a=>{console.log("Work")});
}
EDIT (new cloud function code) :
1st and 2nd bug fixed 3 still continues but I can create users.
exports.createUser = functions.https.onRequest((req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', ' POST, OPTIONS');
res.set('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
res.end();
}
else {
let body = req.body;
console.log(body.email)
console.log("body.password")
const email = body.email;
const password = body.password;
admin.auth().createUser({
email: email,
emailVerified: false,
password: password,
})
.then((userRecord) => {
return res.status(200).send("Successfully created new user: " +userRecord.uid);
})
.catch((error) => {
return res.status(400).send("Failed to create user: " + error);
});
}
});
Cross origin error has many reason, first problem is your current url that probably like a ads url, please change the url pattern and clear browser cache. If you are using a VPN turn off it. The Chrome blocks url that contain ads url. This problem is not happen on production environment.
If your problem not fixed with top solution, use chrome in security disable mode.
Open start menu and type chrome.exe --disable-web-security and
make sure that this headers set in your backend
header('Access-Control-Allow-Origin: *');
header('Access-Control-Request-Method:*');
header('Access-Control-Allow-Headers: Origin,token, Authorization, X-Requested-With, Content-Type, Accept');
header('Access-Control-Allow-Credentials: true');

how to reset password of users whose email is not verified in loopback?

So I ran into this issue.
I have a user who has emailedVerified as false.
So, when I try to reset password for that user as follows it gives me user unverified error.
Person.resetPassword({
email: email
}, function (err) {
if (err) return res.status(422).send(err);
});
So if user has emailVerified as false I created a token for the user with token data as follows:
const tokenData = {
ttl: 900,
scopes: ['reset-password'],
};
user.createAccessToken(tokenData, req, function (err, token) {
//email link with token
});
Now when I try to change password with following request.
/api/people/reset-password?access_token=generated-token and data message as {newPassword: “newPassword”}
I’m getting Access Denied for POST /api/people/reset-password?access_token=token
--Context scopes of Person.setPassword()
This happening only for generated token (either for verified user or non-verified user). If verified user request for password-change its successful which is done by following code.
Person.resetPassword({
email: email
}, function (err) {
if (err) return res.status(422).send(err);
});
I have following settings in person model, which i removed, but still it says access denied.
"restrictResetPasswordTokenScope": true,
"emailVerificationRequired": true,
I found this code in loopback/common/models/user.js:
User.resetPassword = function(options, cb) {
...
if (UserModel.settings.emailVerificationRequired && !user.emailVerified) {
err = new Error(g.f('Email has not been verified'));
err.statusCode = 401;
err.code = 'RESET_FAILED_EMAIL_NOT_VERIFIED';
return cb(err);
}
...
}
Looks like email verification validation only depends on the emailVerificationRequired setting. The value should be false if you want to enable reset password for not verified users:
"emailVerificationRequired": false, // The deletion of this property should also work as I don't see a default value in user.json
If it will not help, I suggest just debug the method above. I think it should be easy to find the problem, when you know the place to search.

Create user with avatar

I want to add an avatar in the user registration, but I don't know how, Please can someone share with me a full example (form, JS front, and JS backend). I'm using SailsJS 1.0 (the stable version) with VueJs.
Thanks in advance .
I figured it out. Watch these platzi tutorials:
https://courses.platzi.com/classes/1273-sails-js/10757-uploading-backend-file/
https://courses.platzi.com/classes/1273-sails-js/10758-uploading-frontend-files/
https://courses.platzi.com/classes/1273-sails-js/10759-downloading-files/
Here is what the videos tell you to do:
npm i sails-hook-uploads.
In api/controllers/entrance/signup.js
Above inputs key add a new key/value of files: ['avatar'],
In the inputs add:
avatar: {
type: 'ref',
required: true
}
In the body of the fn find var newUserRecord and above this add (even if avatar is not required, make sure to do this line, otherwise you will have a "timeout of unconsuemd file stream":
const avatarInfo = await sails.uploadOne(inputs.avatar);
Then in the first argument object of var newUserRecord = await User.create(_.extend({ add:
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
In api/models/User.js, add these attributes to your User model:
avatarFd: {
type: 'string',
required: false,
description: 'will either have "text" or "avatarFd"'
},
avatarMime: {
type: 'string',
required: false,
description: 'required if "avatarFd" provided'
},
Then create a download endpoint, here is how the action would look for it:
const user = await User.findOne(id);
this.res.type(paste.photoMime);
const avatarStream = await sails.startDownload(paste.photoFd);
return exits.success(avatarStream);
Add to the routes a route for this download avatar endpoint.
Then you can display this avatar by pointing the <img src=""> the source in here to this download endpoint.
------APPENDIX-----
----signup.js-----
module.exports = {
friendlyName: 'Signup',
description: 'Sign up for a new user account.',
extendedDescription:
`This creates a new user record in the database, signs in the requesting user agent
by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
(if emailing with Mailgun is enabled) sends an account verification email.
If a verification email is sent, the new user's account is put in an "unconfirmed" state
until they confirm they are using a legitimate email address (by clicking the link in
the account verification message.)`,
files: ['avatar'],
inputs: {
emailAddress: {
required: true,
type: 'string',
isEmail: true,
description: 'The email address for the new account, e.g. m#example.com.',
extendedDescription: 'Must be a valid email address.',
},
password: {
required: true,
type: 'string',
maxLength: 200,
example: 'passwordlol',
description: 'The unencrypted password to use for the new account.'
},
fullName: {
required: true,
type: 'string',
example: 'Frida Kahlo de Rivera',
description: 'The user\'s full name.',
},
avatar: {
}
},
exits: {
success: {
description: 'New user account was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided fullName, password and/or email address are invalid.',
extendedDescription: 'If this request was sent from a graphical user interface, the request '+
'parameters should have been validated/coerced _before_ they were sent.'
},
emailAlreadyInUse: {
statusCode: 409,
description: 'The provided email address is already in use.',
},
},
fn: async function (inputs) {
var newEmailAddress = inputs.emailAddress.toLowerCase();
// must do this even if inputs.avatar is not required
const avatarInfo = await sails.uploadOne(inputs.avatar);
// Build up data for the new user record and save it to the database.
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
var newUserRecord = await User.create(_.extend({
emailAddress: newEmailAddress,
password: await sails.helpers.passwords.hashPassword(inputs.password),
fullName: inputs.fullName,
tosAcceptedByIp: this.req.ip,
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
}, sails.config.custom.verifyEmailAddresses? {
emailProofToken: await sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
emailStatus: 'unconfirmed'
}:{}))
.intercept('E_UNIQUE', 'emailAlreadyInUse')
.intercept({name: 'UsageError'}, 'invalid')
.fetch();
// If billing feaures are enabled, save a new customer entry in the Stripe API.
// Then persist the Stripe customer id in the database.
if (sails.config.custom.enableBillingFeatures) {
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
emailAddress: newEmailAddress
}).timeout(5000).retry();
await User.updateOne(newUserRecord.id)
.set({
stripeCustomerId
});
}
// Store the user's new id in their session.
this.req.session.userId = newUserRecord.id;
if (sails.config.custom.verifyEmailAddresses) {
// Send "confirm account" email
await sails.helpers.sendTemplateEmail.with({
to: newEmailAddress,
subject: 'Please confirm your account',
template: 'email-verify-account',
templateData: {
fullName: inputs.fullName,
token: newUserRecord.emailProofToken
}
});
} else {
sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
}
// add to pubilc group
const publicGroup = await Group.fetchPublicGroup();
await Group.addMember(publicGroup.id, newUserRecord.id);
}
};

Sails.JS + sails-auth + passport-openidconnect

I'm trying to implement passport-openidconnect into my Sails app. I've installed sails-auth, passport, passport-local, passport-http, and passport-openidconnect, all of which are required to start the sails app. I copied the contents of this file to get a passport config since the sails app was already started when I began implementing. This is my config file so far:
module.exports.passport = {
openid_connect: {
name: 'OpenID Connect',
protocol: 'oauth2',
strategy: require('passport-openidconnect').OAuth2Strategy,
options: {
clientID: '',
clientSecret: ''
}
}
};
I based this off some of the default options that were in the config/passport.js file mentioned above.
I've searched for setup examples for the OpenID Connect, but haven't been able to find anything so far. Has anyone implemented this in their own project and could give me some pointers? Thanks!
I've implemented passport in sails, with passport-local, passport for Google/FB/Twitter, but without sails-auth !
I don't know passport-openID but this should be nearly the same.
First you need to add passport middleware like this in your config/http.js
Then you have to create the different strategy in config/passport.js (exemple with FacebookStrategy, it should
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy
, FacebookStrategy = require('passport-facebook').Strategy
var verifyExtHandler = function (token, tokenSecret, profile, done) {
checkAuthExt(profile, done);
};
var verifyHandler = function (mail, password, done) {
checkAuth(mail, password, done);
};
// 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) {
user.password = null;
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a username and password), and invoke a callback
// with a user object.
passport.use(new LocalStrategy({
usernameField: 'mail',
passwordField: 'password'
}, verifyHandler));
// Remplacer les 'XXXXX' par vos clés et 'yourhost.com' par votre nom de domaine
passport.use(new FacebookStrategy({
clientID: "XXXXXX",
clientSecret: "XXXXXX",
callbackURL: "http://yourhost.com/auth/facebook"
}, verifyExtHandler));
And you need to configure your routes (config/routes.js) :
'/auth/facebook': 'AuthController.facebook',
'/auth/facebook/callback': 'AuthController.facebook'
Then in your controller :
facebook: function (req, res) {
passport.authenticate('facebook', {
failureRedirect: '/auth/login'
}, function (err, user) {
if (err) {
return console.log(err);
}
req.logIn(user, function (err) {
if (err) {
console.log(err);
res.serverError();
return;
}
return res.redirect('/');
});
})(req, res);
}
Hope that helps !

Meteor Facebook login (Meteor.loginWithFacebook) issue extracting public profile, email and user_friends

Trying to get Meteor Facebook login to work. It functions fully in that it uses Facebook API and requests the correct permissions from the users account and then logs in successfully.
The problem is it doesn't save the permission requested information even though its been approved and only the basic name and ID are available in Meteor.user().services.facebook. Is this code not working because it's not saving the users details on login? I can't find a resource that details how to save or extract the other data.
Simply trying to console log the data to see that it's been extracted out of the Facebook user account on log in.
Within Meteor.isClient code:
Template.login.events({
'click #facebook-login': function(event) {
Meteor.loginWithFacebook({ requestPermissions: ['email', 'public_profile', 'user_friends', 'user_likes']}, function(err){
if (err) {
throw new Meteor.Error("Facebook login failed");
}
console.log(Meteor.user().services.facebook.name);
console.log(Meteor.user().services.facebook.id);
console.log(Meteor.user().services.facebook.email);
console.log(Meteor.user().services.facebook.gender);
});
},
'click #logout': function(event) {
Meteor.logout(function(err){
if (err) {
throw new Meteor.Error("Logout failed");
}
});
}
The config code:
ServiceConfiguration.configurations.remove({
service: 'facebook'
});
ServiceConfiguration.configurations.insert({
service: 'facebook',
appId: 'correctAppID',
secret: 'CorrectSecret'
});
For Facebook v2.4 API after you have requested for certain permissions you can then access them by making a graph API call and requesting them with a valid auth token. The code is as follows:
if (user.hasOwnProperty('services') && user.services.hasOwnProperty('facebook') ) {
var result = Meteor.http.get('https://graph.facebook.com/v2.4/' + user.services.facebook.id + '?access_token=' + user.services.facebook.accessToken + '&fields=first_name, last_name, birthday, email, gender, location, link, friends');
console.log(result.data.first_name);
console.log(result.data.last_name);
console.log(result.data.birthday);
console.log(result.data.email);
console.log(result.data.gender);
console.log(result.data.location);
console.log(result.data.link);
console.log(result.data.friends);
}