Why is the accesstoken not set by PFFacebookUtils logInInBackgroundWithReadPermissions method? - facebook-login

For reference: I am using the latest Facebook IOS SDK v4 and the latest Parse v1.7.4 and ParseFacebookUtilsV4 SDK.
So I am using PFFacebookUtils loginInBackgroundWithReadPermissions:block: method to have the user login with Facebook credentials and create a PFUser with those credentials.
And then proceeded to make a facebook graph request with FBSDKGraphRequest which from what i read, assumes a valid token has been set (there is no passing of a token parameter to it).
But the facebook graph request failed, and in tracing the issue, I noticed that the "access token" is never set by the loginInBackgroudnWithReadPermissions method. What this method only does is to create a PFUser and a Session instance in Parse, and store there the session token string, but it does not set the currentAccessToken . When I do a [[FBSDKAccessToken currentAccessToken] tokenString] call within the block, I get (null). But if I read the "token string" from the PFSession class I get the "token string" store in the Session class instance in Parse.
See the code below:
- (IBAction)fbLoginAction:(id)sender {
// Set permissions required from the facebook user account
NSArray *permissionsArray = #[ #"email", #"user_friends"];
// Login PFUser using Facebook
[PFFacebookUtils logInInBackgroundWithReadPermissions:permissionsArray block:^(PFUser *user, NSError *error) {
if (!user) {
NSLog(#"Uh oh. The user cancelled the Facebook login.");
} else if (user.isNew) {
NSLog(#"User signed up and logged in through Facebook!");
// Check if the current token has been set
NSLog(#"self.myCurrentToken string = %#", [[FBSDKAccessToken currentAccessToken] tokenString]); // returns (null)
// Get the token string from the PFSession
[PFSession getCurrentSessionInBackgroundWithBlock:^(PFSession *session, NSError *error) {
NSString *tokenString = session.sessionToken;
NSLog(#"Session token = %#", tokenString);
}];
}
And here is the debug console output
So I am not sure what is happening.
IF the PFSession token is set to something shouldn't the FBSDKAccessToken currentAccessToken not be returning this same PFsession token?
And if I were to set the accessToken manually, how can I convert the tokenstring that is stored in Parse to an FBSDKAccessToken instance? There seems to be no method to do this?
thanks

This is happening because you need to add the following code to the bottom of AppDelegate didFinishLaunchingWithOptions :
return [[FBSDKApplicationDelegate sharedInstance] application:application
didFinishLaunchingWithOptions:launchOptions];
By adding the code, your currentAccessToken will no longer be nil and you can make requests to FB right away.
FYI, I was able to discover the acccessToken was nil because I got the following error every time I tried to use FBSDKGraphRequest. So if anyone else gets the error below, make sure your accessToken is not nil even after you log in!
{"error":{"message":"An active access token must be used to query information about the current user.","type":"OAuthException","code":2500}}

#jhk:
Yes. I got an explanation from Facebook support. Login in through Parse is a two step process. First step, is the authorization step, where the user authenticates with Facebook. It switches to Facebook app, and the app receives Facebook access token when it completes successfully. And the second step is , the app authenticates with Parse (i.e your app) using the Facebook token. If it matches existing user/session, you are logged in successfully. However, I was deleting the PFSession manually from the parse backend, while testing every single time I tried to login, so that invalidated the session stored in the backend, and after the 1st step, when the app gets authorized and gets the accesstoken, the system realizes that the local session doesn't match the parsed stored session and invalidated the accesstoken, it sets it to nil. That is why I am seeing the token being set to nil.

Related

Get ACCESS_TOKEN_SCOPE_INSUFFICIENT error migrating to People API

I have a desktop Java app that I am migrating from Google Contacts API to People API. I have some of it working. For example, I can retrieve contact information. But when I tried to create a new contact, I get the following error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
POST https://people.googleapis.com/v1/people:createContact
{
"code" : 403,
"details" : [ {
"#type" : "type.googleapis.com/google.rpc.ErrorInfo",
"reason" : "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
} ],
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Request had insufficient authentication scopes.",
"status" : "PERMISSION_DENIED"
}
Here's the relevant code:
protected void createContact() throws Exception {
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
PeopleService service = new PeopleService.Builder(
httpTransport, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
Person contactToCreate = new Person();
List<Name> names = new ArrayList<Name>();
names.add(new Name().setGivenName("John").setFamilyName("Doe"));
contactToCreate.setNames(names);
Person createdContact = service.people().createContact(contactToCreate).execute();
System.out.println("CREATED Contact: " + createdContact.getNames().get(0).getDisplayName());
}
protected Credential authorize(String scope, String subDir) throws Exception {
File dataStoreDir = new File(System.getProperty("user.home"), ".store/myapp/" + cfg.dataStore + "/" + subDir);
// initialize the transport
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
// initialize the data store factory
dataStoreFactory = new FileDataStoreFactory(dataStoreDir);
// load client secrets
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
new InputStreamReader(SyncMgr.class.getResourceAsStream("/client_secrets.json")));
if (clientSecrets.getDetails().getClientId().startsWith("Enter")
|| clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
System.out.println(
"Enter Client ID and Secret from https://code.google.com/apis/console/?api=calendar "
+ "into /client_secrets.json");
System.exit(1);
}
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
}
When I first ran it, I had the scope set to CONTACTS_READONLY. And I got the consent screen. But then I changed the scope to CONTACTS when I added the code to create a new contact. And that's when I got the ACCESS_TOKEN_SCOPE_INSUFFICIENT error.
I saw in another post that I need to force your app to reauthorize the user when you change the scope, so that you get the consent screen again. But I'm not sure how to do that. Any suggestions?
Thanks.
UPDATE 1/4/22
I tried Gabriel's suggestion of removing access to the application. After removing access, I ran the application again. This time I got this error on the execute() call:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
POST https://oauth2.googleapis.com/token
{
"error" : "invalid_grant",
"error_description" : "Token has been expired or revoked."
}
And even the execute() statement that worked before to retrieve contacts is giving the same error now.
My application also used the Calendar API. I didn't touch that code. But when I try to use it, I get the same "invalid_grant" error. What do I do now?
You appear to be using the People.createContact method. If we take a look at the documentation we will see that this method requires a consent to the following scope of permissions from the user
Now if we check your code you apear to be using
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
Which is the exact scope needed. But you oringally had readonly there. So when your code ran the first time the user authorized to the read only scope and not the full contacts scope and your stuck.
The key here is this section of code.
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
Kindly note I am not a Java developer I am a .net developer. The libraries are very close and i have been helping with questions this in both languages for years.
dataStoreFactory is where the consent from the user is stored. There should be a json file some where in your directory structure with the users name associated with it this is how your system reloads it. When your code runs it will look for a file in that directory with cfg.gUser name.
There should be a way in the Java client library to force it to rerequest authorization of the user. prompt type force. But i will have to look around to see how to do it in java.
The easiest solution now would be to find that directory and delete the file for the user or just change the users name cfg.gUser to cfg.gUser +"test" or something this will cause the name to change and the file name as well. Forcing it to prompt the user for authorization again.
This time when it requests consent take note which scope of permissions it asks for.
Token has been expired or revoked.
This is probably due to the fact that your refresh tokens are expiring. When your application is in the testing phase the refresh tokens are expired or revoked automatically by google after seven days.
This is something new and something that Google added in the last year or so. Unfortunately the client libraries were not designed to request access again if the refresh token was expired in this manner.
If you are looking to retrieve the consent screen again you can remove access to your application from your account settings by following the steps in this documentation and then try to authorize the app again. As you mentioned, the error received is due to the scope that was granted with authorization was CONTACTS_READONLY instead of CONTACTS when checking the authorization scope for this specific create contacts method.

How does one keep a user logged in with Django Rest Framework?

I'm new to Django coming from the Firebase world, where authentication and keeping a user logged in is super easy.
In learning Django (Rest Framework) I came to find out that you can log in a user, get a token and save the token in Cookies to reuse is next time that same user goes into the website. Is this the best way to keep a user logged in?
So far, I can log a user in, get their token and some additional info, but I'm not sure how to prevent this from happening over and over again. I'd like to know how to keep the user logged in.
Also, whenever the user gets back on the browser, do I place a POST request to get their own information (if needed to display on the screen)? Always?
I'm very confused as to how authentication/logging in works.
An usual way to handle this problem is to use Jwt auth.
You will issue a short lived token alongside a long lived refresh token to your consumer.
https://github.com/jpadilla/django-rest-framework-jwt
On your frontend side you can implement an automatic refresh mechanism when the token expire.
Example with React: https://medium.com/#monkov/react-using-axios-interceptor-for-token-refreshing-1477a4d5fc26
On browser side, it's depend. For example with single page app, you can fetch info only one and store them in a store.
For multi page app, you could still use cookie or local storage to persist data.
Each Request is anonymous/new, even after you login.
Why I keep loggedin?
Once you logged in, server usually reuturn a token and save it in your local browser. Next time you send request, you can add the token in your request. Then server will know it is still the same user.
What is token?
There a many kinds of token: session token, jwt, basic token...
Token is a string of your identity, and jwt(JSON Web Tokens) is one of the most popular authentication(CORS) solution. This is how original jwt looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
And this is how actual jwt data looks like:
# HEADER:ALGORITHM
{
"alg": "HS256",
"typ": "JWT"
}
# PAYLOAD:DATA
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
# VERIFY SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
Where do I keep token
Token is storage in your local cookies. For each request, you can get token from local cookies and add to request head. For example, in React:
import Cookies from "universal-cookie"
localCookies = new Cookies();
...
..
fetch(`/api/logs/:id`, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'JWT ' + localCookies.get('token'),
}
})
...
..

Posting data to Silex app ( jwt authentication using cnam/security-jwt-service-provider )

I am creating web service using silex micro framework. This is first time i am using it and I dont have prior knowledge on symfony. I could be able to understand how silex works and how to write controller providers , service providers etc ..
I couldnt work out authentication for my webservice.
I want to use JWT authentication and I found this cnam/security-jwt-service-provider extending firebase/php-jwt.
I set it up right and I get this output when I access protected resource
{"message":"A Token was not found in the TokenStorage."}
which is correct.
Question: I want to post the username , password to application and get token back. ( username=admin , password=foo )
I am using postman chrome extension ( also used Advanced rest client ) to post values to url ( http://silex.dev/api/login)
Post data I am sending
Key=> username Value=> admin
Key=> password Value=> foo
I also tried
Key=> _username Value=> admin
Key=> _password Value=> foo
Aldo tried key vaule pairs in basic auth headers.
response I get id
{
"success": false,
"error": "Invalid credentials"
}
When I debug the Application I see no post data at all.
$vars = json_decode($request->getContent(), true);
I get null $var.
PS: I know I will get a valid token back if I get these values posted correctly because I edited values from empty to correct values in breakpoint.
Apparently I should send data in json format and should set content-type appication/json
eg:
{
"_username":"admin",
"_password":"foo"
}
and response will something be like
{
success: true
token: "eyJ0eXAiOisKV1diLCJfbGgiOhJIjzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJleHAiOjE0Mzk5MDUxMjh9.DMdXAv2Ay16iI1UQbHZABLCU_gsD_j9-gEU2M2L2MFQ"
}

Setting user's email on Facebook signup with Parse.com

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?

How to properly use app and user tokens with the Facebook Score API on iOS

I'm trying to post a high score to the Facebook Graph API in iOS, but having no luck getting past all the token stuff. I've modified the iOS library to store the app token and the user token and I swap the app token in when making the post to https://graph.facebook.com/{my user id}/scores, but I keep getting an error back about needing a user token. When I swap the user token in, it tells me I need an app token.
I figured it might be something like the user token getting switched back in before the request completed but I have double and triple checked that the request comes back with the app token I received as the access_token post value. According to the debugging I've been doing, I need both a user access token and an app access token.
*After called oauth/access_token*
NSString *tempToken = [[[NSString alloc] initWithData:[request responseText] encoding:NSUTF8StringEncoding] autorelease];
//Response is something like "access_token={APP ID}|{SOME HASHED VALUE}"
[fb setAppAccessToken:[tempToken substringFromIndex:13]];
[fb setAccessToken:[fb appAccessToken]];
NSMutableDictionary *scoreParams = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"20", #"score", nil];
[fb requestWithGraphPath:#"{MY USER ID}/scores" andParams:scoreParams andHttpMethod:#"POST" andDelegate:self];
[fb setAccessToken:[fb userAccessToken]];
With USER ACCESS TOKEN "(#15) This method must be called with an app access_token."
With APP ACCESS TOKEN "A user access token is required to request this resource."
I'm also using publish_actions as a permission.