Setting user's email on Facebook signup with Parse.com - facebook-graph-api

I'm using Parse.com and trying to set up user sign up with Facebook.
Upon authentication with Facebook for the first time a beforeSave is called on _User to fetch additional user details:
function UserBeforeSave(request, response){
var user = request.object,
auth = user.get('authData');
// check if user is newly registered
if (!user.existed()) {
// Check if a user signs up with facebook
if (Parse.FacebookUtils.isLinked(request.object)) {
// Query Graph API for user details
Parse.Cloud.httpRequest({
url:'https://graph.facebook.com/v2.2/me?access_token=' + auth.facebook.access_token,
success:function(httpResponse){
// Map facebook data to user object
if (httpResponse.data.first_name) request.object.set('first_name', httpResponse.data.first_name);
if (httpResponse.data.last_name) request.object.set('last_name', httpResponse.data.last_name);
if (httpResponse.data.email) request.object.set('email', httpResponse.data.email);
response.success();
},
error:function(httpResponse){
console.error(httpResponse);
response.error();
}
});
} else {
response.success();
}
} else {
response.success();
}
}
Problem is that that email line is actually breaking the operation with error:
Can't modify email in the before save trigger
I tried moving this code to the afterSave but the authdata is not available there making it difficult to call the FB API. The email is therefore left blank at the moment.
I'm assuming this is a very common use case of integrating Parse with the Facebook API, am I missing something in the integration process that automatically fetches the email?

I just do the graph query client-side and set email there. This works fine.
Is there a reason you want to do it in the before/afterSave on the User?

Related

Facebook Matching API - Returns empty data for any other user

I have a problem when I use the Facebook Checkbox Plugin in order to connect my users to a Facebook chatbot. When they click and the checkbox is checked, I get their user reference, and sending him/her a message, I get the user page-scoped id.
Using this user page-scoped id, I should be able to get the user app-scoped id, that I need to get more information from this user.
In order to to this, I use the facebook Matching API, and it works great for my administrator user, but as soon as I login using any other user, even if it is registered as a developer, the data that I get from the matching API is empty.
[https://developers.facebook.com/docs/messenger-platform/identity/id-matching]
Anybody has an idea about what could be happening here? My app is live (not approved), and I believe the permissions and tokens are right... If there is a problem, it should be about tokens, but I'm not sure about this.
Here, some of my code:
const accessToken = config.facebook.unlimitedPageAccessToken;
const clientSecret = config.facebook.clientSecret;
const appsecretProof = CryptoJS.HmacSHA256(accessToken, clientSecret).toString(CryptoJS.enc.Hex);
request({
url: 'https://graph.facebook.com/v2.10/'+ recipientId +'/ids_for_apps',
qs: { access_token: accessToken, appsecret_proof: appsecretProof }
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
body = JSON.parse(body);
console.log("data --> " + JSON.stringify(body, null, 4));
const userAppId = body.data[0].id;
return userAppId;
} else {
console.error("Error trying to translate ID's.");
}
});
As I said, when I log in with any other user than the administrator, I get this:
{
"data": []
}
For every Facebook page, a user has a different psid. So until you get that page scoped id, you won't be able to send them a message. So may be what you can do is link the users to the page first to initialize the conversation.

Store UserTokens generated by ASP.Net Core identity for external login provider

I am using Facebook as external login of ASP.Net Core identity.
I would like, even if the user logged in with Facebook, the user to fill his profile on the website.
For that I use the ExternalLoginCallback method, from which I would like to get data from Facebook such as date of birth, location (country), ...
One issue is if the user unchecked some of the permissions, the default call to Facebook fails:
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
I would also need to do some additional checks on user data, which would require calling directly the Graph API.
My blocking points:
- In the ExternalLoginCallback method, I would need to separate the 'country' and 'birthday' to avoid the Facebook API to return an error in case of the user didn't grant the permission
- For that I would need the the user access_token (and for additional calls in the method), I don't see how to get it even if it is used by the Facebook Identity framework
- Once the profile created, I would like to get access to this access_token, which should be stored in the UserTokens table (I guess?), but I can't find it there, the table is empty. (my DbContext is a class extending IdentityDbContext<AppUser, AppRole, long>, don't know if it has an impact)
I have found this answer https://stackoverflow.com/a/42670559/4881677 which may help, but not sufficient.
Any help? :)
In order to store the user Facebook token, it requires to specify it in the options (not stored by default).
var fo = new FacebookOptions();
fo.SaveTokens = true;
From there we can call the graph method permissions to get the available permissions: https://graph.facebook.com/me/permissions?access_token={token}
Then it can be read with something like this:
foreach (var perm in data)
{
perms.Add((string)perm["permission"], (string)perm["status"]);
}

Identity Server 3 Facebook Login Get Email

Identity server is implemented and working well. Google login is working and is returning several claims including email.
Facebook login is working, and my app is live and requests email permissions when a new user logs in.
The problem is that I can't get the email back from the oauth endpoint and I can't seem to find the access_token to manually request user information. All I have is a "code" returned from the facebook login endpoint.
Here's the IdentityServer setup.
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then of course I've customized the AuthenticateLocalAsync method, but the claims I'm receiving only include name. No email claim.
Digging through the source code for identity server, I realized that there are some claims things happening to transform facebook claims, so I extended that class to debug into it and see if it was stripping out any claims, which it's not.
I also watched the http calls with fiddler, and I only see the following (apologies as code formatting doesn't work very good on urls. I tried to format the querystring params one their own lines but it didn't take)
(facebook.com)
/dialog/oauth
?response_type=code
&client_id=xxx
&redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&scope=email
&state=xxx
(facebook.com)
/login.php
?skip_api_login=1
&api_key=xxx
&signed_next=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_
&display=page
&locale=en_US
&logger_id=xxx
(facebook.com)
POST /cookie/consent/?pv=1&dpr=1 HTTP/1.1
(facebook.com)
/login.php
?login_attempt=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx
&lwv=100
(facebook.com)
/v2.7/dialog/oauth
?redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&state=xxx
&scope=email
&response_type=code
&client_id=xxx
&ret=login
&logger_id=xxx
&hash=xxx
(identity server)
/id/signin-facebook
?code=xxx
&state=xxx
I saw the code parameter on that last call and thought that maybe I could use the code there to get the access_token from the facebook API https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
However when I tried that I get a message from the API telling me the code has already been used.
I also tried to change the UserInformationEndpoint to the FacebookAuthenticationOptions to force it to ask for the email by appending ?fields=email to the end of the default endpoint location, but that causes identity server to spit out the error "There was an error logging into the external provider. The error message is: access_denied".
I might be able to fix this all if I can change the middleware to send the request with response_type=id_token but I can't figure out how to do that or how to extract that access token when it gets returned in the first place to be able to use the Facebook C# sdk.
So I guess any help or direction at all would be awesome. I've spent countless hours researching and trying to solve the problem. All I need to do is get the email address of the logged-in user via IdentityServer3. Doesn't sound so hard and yet I'm stuck.
I finally figured this out. The answer has something to do with Mitra's comments although neither of those answers quite seemed to fit the bill, so I'm putting another one here. First, you need to request the access_token, not code (authorization code) from Facebook's Authentication endpoint. To do that, set it up like this
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));
return Task.FromResult(0);
}
}
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then, you need to catch the response once it's logged in. I'm using the following file from the IdentityServer3 Samples Repository, which overrides (read, provides functionality) for the methods necessary to log a user in from external sites. From this response, I'm using the C# Facebook SDK with the newly returned access_token claim in the ExternalAuthenticationContext to request the fields I need and add them to the list of claims. Then I can use that information to create/log in the user.
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
var externalUser = ctx.ExternalIdentity;
var claimsList = ctx.ExternalIdentity.Claims.ToList();
if (externalUser.Provider == "Facebook")
{
var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
}
if (externalUser == null)
{
throw new ArgumentNullException("externalUser");
}
var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
if (user == null)
{
ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
}
else
{
ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
}
}
And that's it! If you have any suggestions for simplifying this process, please let me know. I was going to modify this code to do perform the call to the API from FacebookAuthenticationOptions, but the Events property no longer exists apparently.
Edit: the GetAdditionalFacebookClaims method is simply a method that creates a new FacebookClient given the access token that was pulled out and queries the Facebook API for the other user claims you need. For example, my method looks like this:
protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
}

Meteor share sessions data between client and server

I'm building a restricted signup. I want user with a specific code passed in a url to be able to signup and not others. I'm using the accounts package.
I can prevent account creation in the Accounts.onCreateUser method. I'm looking for a way to tell the server if the client had an authorised signup code. With a classic form (email+password) I can just add an extra hidden field. How can I achieve the same result if the user signs up with let's say Facebook?
Since Meteor doesn't use cookies, I can't store this info in a cookie that the server would access. Session variable are not accessible server side. And since I'm not controlling what got send with the account-facebook creation, I can't use a Session variable on the client side that I'd pass along when the user presses sign up.
Any idea"?
Just add the special token to the user object being passed to Accounts.createUser():
var user = {
email: email,
password: password,
profile: {
token: token
}
};
Accounts.createUser(user, function (error, result) {
if (error) {
console.log(error)
}
});
On the server side you can access this in the Accounts.onCreateUser():
Accounts.onCreateUser(function(options, user) {
console.log(options);
console.log(user);
});
I think it's in the options variable that you will find your token, so it would be options.profile.token.
for me, the best option here was passing in custom parameters to loginbuttons.
see the package docs:
https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3
Where it outlines the below:
accountsUIBootstrap3.setCustomSignupOptions = function() {
return {
mxpDistinctId: Session.get('mxpdid'),
leadSource: Session.get('leadSource')
}
};

Retrieving scores with Application Token

So I have an app set up, and I'm trying to send scores via a server rather than from the application. This allows me to keep scores longer term, whilst also having the social advantages of Facebook.
Now, the problem I have comes in retrieving the scores using the Application Token. I can post absolutely fine using either the Application Token or a User Token, but when retrieving the scores with the Application Token I receive the following:
{
"data": [
]
}
If it was flat out not working or a permissions issue I'd expect to receive an error returned, but the fact it returns an empty array is puzzling. More puzzling is that using a User Access Token retrieves the score absolutely fine, so it's clearly arriving correctly into the Facebook backend.
Is this just a problem with using an App Access Token in this situation? The documentation says that I should be able to use one, but maybe it's mistaken?
I'd also like to clarify that I've run this both in code and via the Graph Explorer, always with no success.
Make sure that you have granted user_games_activity and friends_games_activity permissions
on developers.facebook.com/tools/explorer
from above link you will get an application access_token and add it in your code like this
public void sendDataToFacebookGraphServer()
{
// TODO Auto-generated method stub
final Session session = Session.getActiveSession();
List<String> permissions = session.getPermissions();
if (!isSubsetOf(PERMISSIONS, permissions)) {
Session.NewPermissionsRequest newPermissionsRequest = new Session
.NewPermissionsRequest(UnityPlayer.currentActivity, PERMISSIONS);
session.requestNewPublishPermissions(newPermissionsRequest);
return;
}
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("https://graph.facebook.com/user_id/scores");
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
pairs.add(new BasicNameValuePair("score", "3000"));
// add this line and try
pairs.add(new BasicNameValuePair("access_token", "add_app_access_token_here"));
try{
post.setEntity(new UrlEncodedFormEntity(pairs));
}
catch(UnsupportedEncodingException e)
{
}
try{
response = client.execute(post);
Log.i("*********Response*******************************************************", response.toString());
UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
Toast.makeText(UnityPlayer.currentActivity,""+response.toString(),Toast.LENGTH_LONG).show();
}
});
}
catch (IOException e1)
{
}
}
Is this supposed to work with the app access token? I don't think it is.
According to the Scores Documentation you can
Retrieve a user's score with the user or app access token (/USER_ID/scores)
Retrieve a user's friends' scores for your app with the user access token (/APP_ID/scores)
Retrieve a user's friends' scores in any app with the user access token (/USER_ID/scores) - though this one respects those users' privacy settings so you won't get an answer for users whose game/app activity is private