Facebook App development Nuances

Some usefull references

  1. FB Developer Page
  2. My Apps Page
  3. change username
  4. Web apps JS
  5. JS SDK
  6. APIs
  7. Graph API
  8. SDKs
  9. PHP SDK
  10. Access token with PHP
  11. Access Token with PHP from JS
  12. Android SDK

Sequence

Sequence Diagram1

  1. On access of an url or in welcome page the Facebook Login button is shown. The user will click the FB button to login into the Java web application. On click of that button a Facebook URL will be invoked.

  2. Facebook will validate the application ID and then will redirect to its login page.

    • User will enter the FB login credentials and submit the form.
    • Facebook will validate the credentials and then redirect back to the browser with a request to forward to the redirect_url.
    • Redirect_url is the URL in our application which will take care of further processing.
  3. Browser will call the redirect url.

  4. Redirect URL page will again call the Facebook to request for access_token.

  5. Facebook on validation success will respond back with access_token.

  6. Redirect URL page will again call the Facebook to request for user data by sending the access_token.

  7. Facebook on validating the access_token will respond back with user data requested.

  8. Redirect URL page will forward to a page showing user data in the client browser.

Sequence Diagram2

Some usefull aspects

Test Users

The test user need to register as Facebook developer (if he not reregistered already)

Link: https://developers.facebook.com/async/registration/

After that he need to approve the request from the app

Then he need to approve the request at this link Link: https://developers.facebook.com/settings/developer/requests

Facebook adding tester users and admin is: (pending), what does that mean?

Found the answer, its a bit silly but if anyone else gets the same issue...

After you add a new user to the "Testers" he gets a notification that he must accept, so the "Pending" will go away, and the user will be activated.

This notification does not get to the Facebook home page, only to the "Developer section" of that user. As @rmp251 mentioned, that can be found on this page

Remove Apps Consent

https://www.facebook.com/settings/?tab=applications

Usefull rerences for FB app development

Graph API reference

https://developers.facebook.com/docs/graph-api

Graph API V16(latest as on 10-05-2023) changelog

https://developers.facebook.com/docs/graph-api/changelog/version16.0

Graph API Old (eg: V2.6) changelog

https://developers.facebook.com/docs/graph-api/changelog/archive/version2.6/

Graph Explorer Tool

https://developers.facebook.com/tools/explorer

Graph API permissions

https://developers.facebook.com/docs/permissions/reference/

  1. Create FB App

Created App

  1. Under Settings >> Basic

    • set App domains
    • get App Id and App secret
      Created App url will be like https://developers.facebook.com/apps/App-ID/settings/basic/
    • Under Website >> Site URL set path of your loginRedirectURL
  2. Download facebook graph sdk

Graph SDK Git

composer require facebook/graph-sdk
  1. Under Products >> Facebook Login >> Settings
    set Valid OAuth Redirect URIs
  2. replace App Id, App secret and OAuth Redirect URI in the below code

Possible Errors

PHP Code for FB Login

This is based on this

<?php
require 'vendor/autoload.php';
session_start();
//session_destroy();exit;// uncomment this line for testing
$app_id = "YOUR_APP_ID";
$app_secret = "YOUR_APP_SECRET";
$OAuthRedirectURI = "YOUR_POST_LOGIN_URL";
$fb = new Facebook\Facebook([
                                 'app_id' => $app_id,
                                 'app_secret' => $app_secret,
                                 'default_graph_version' => 'v2.5',
                                ]);
    $helper = $fb->getRedirectLoginHelper();
    $permissions = ['email']; // optional
    try {
            if (isset($_SESSION['facebook_access_token'])) {
                $accessToken = $_SESSION['facebook_access_token'];
            } 
            else 
            {
              $accessToken = $helper->getAccessToken();
            }
    } catch(Facebook\Exceptions\facebookResponseException $e) {
            // When Graph returns an error
            echo 'Graph returned an error: ' . $e->getMessage();
            exit;
    } catch(Facebook\Exceptions\FacebookSDKException $e) {
            // When validation fails or other local issues
            echo 'Facebook SDK returned an error: ' . $e->getMessage();
            exit;
    }
    if (isset($accessToken)) {
        if (isset($_SESSION['facebook_access_token'])) {
            $fb->setDefaultAccessToken($_SESSION['facebook_access_token']);
        } 
        else 
        {
            // getting short-lived access token
            $_SESSION['facebook_access_token'] = (string) $accessToken;
              // OAuth 2.0 client handler
            $oAuth2Client = $fb->getOAuth2Client();
            // Exchanges a short-lived access token for a long-lived one
            $longLivedAccessToken = $oAuth2Client->getLongLivedAccessToken($_SESSION['facebook_access_token']);
            $_SESSION['facebook_access_token'] = (string) $longLivedAccessToken;
            // setting default access token to be used in script
            $fb->setDefaultAccessToken($_SESSION['facebook_access_token']);
        }

        // getting basic info about user
        try {
            $profile_request = $fb->get('/me?fields=name,first_name,last_name,email');
            $requestPicture = $fb->get('/me/picture?redirect=false&height=200'); //getting user picture
            $picture = $requestPicture->getGraphUser();
            $profile = $profile_request->getGraphUser();
            $fbid = $profile->getProperty('id');           // To Get Facebook ID
            $fbfullname = $profile->getProperty('name');   // To Get Facebook full name
            $fbemail = $profile->getProperty('email');    //  To Get Facebook email
            $fbpic = "<img src='".$picture['url']."' class='img-rounded'/>";
            # save the user nformation in session variable
            $_SESSION['fb_id'] = $fbid.'</br>';
            $_SESSION['fb_name'] = $fbfullname.'</br>';
            $_SESSION['fb_email'] = $fbemail.'</br>';
            $_SESSION['fb_pic'] = $fbpic.'</br>';
            echo "Done setting SESSION Vars!! go to <a href=\"profile.php\">Profile page</a> or <a href=\"fbAlbums.php\">Albums page</a>";
        } 
        catch(Facebook\Exceptions\FacebookResponseException $e) {
            // When Graph returns an error
            echo 'Graph returned an error: ' . $e->getMessage();
            session_destroy();
            // redirecting user back to app login page
            header("Location: ./");
            exit;
        } catch(Facebook\Exceptions\FacebookSDKException $e) {
            // When validation fails or other local issues
            echo 'Facebook SDK returned an error: ' . $e->getMessage();
            exit;
        }
        // redirect the user to the profile page if it has "code" GET variable
        if (isset($_GET['code'])) {
            //die('Location: profile.php');
            header('Location: profile.php');
        }
    } 
    else 
    {
        // replace your website URL same as added in the developers.Facebook.com/apps e.g. if you used http instead of https and you used            
        //$loginUrl = $helper->getLoginUrl('https://phpstack-21306-56790-161818.cloudwaysapps.com', $permissions);
        $loginUrl = $helper->getLoginUrl($OAuthRedirectURI, $permissions);
        echo '<a href="' . $loginUrl . '">Log in with Facebook!</a>';
    }
    ?>

With Android

Reference
Reference
FBLoginSample on GitHub
Getting Hash Key for debugging
Getting Hash Key for release

Invalid key hash. The key hash does not match any stored key hashes

Hash Key issue
Solution:

  1. Note the new Key shown in the error message .
  2. copy it in FB developer console
  3. try again
keytool -exportcert -alias YOUR_RELEASE_KEY_ALIAS -keystore YOUR_RELEASE_KEY_PATH | openssl sha1 -binary | openssl base64

Android Code

package com.example.causelist;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.facebook.AccessToken;
import com.facebook.AccessTokenTracker;
import com.facebook.CallbackManager;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.GraphRequest;
import com.facebook.GraphRequestAsyncTask;
import com.facebook.GraphResponse;
import com.facebook.HttpMethod;
import com.facebook.LoginStatusCallback;
import com.facebook.Profile;
import com.facebook.login.LoginManager;
import com.facebook.login.LoginResult;
import com.facebook.login.widget.LoginButton;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
//https://developers.facebook.com/docs/facebook-login/android/?sdk=maven
//project:My First Test App
//App ID: 1497939987182600
 /*
keytool -exportcert -alias androiddebugkey -keystore "C:\Users\DELL\.android\debug.keystore" | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" sha1 -binary | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" base64
keytool -exportcert -alias androiddebugkey -keystore "C:\Users\DELL\.android\debug.keystore" | "C:\openSSL\bin\openssl" sha1 -binary | "C:\openSSL\bin\openssl" base64
Keystore password:123456
ga0RGNYHvNM5d0SLGQfpQWAPGJ8=
By code
+gztLysQuYVmCj1tOoQdE1ib6is=
//https://stackoverflow.com/questions/23674131/android-facebook-integration-with-invalid-key-hash
*/
 /*
 Error: App not setup: This app is still in development mode
 https://stackoverflow.com/questions/30085246/app-not-setup-this-app-is-still-in-development-mode
*/
public class FbLoginActivity extends AppCompatActivity {
     private static final String EMAIL = "email";
     CallbackManager callbackManager;
     LoginButton loginButton;
     TextView txtFBUser;
     String logTag = "FB_LOGIN_TEST";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fb_login);
        txtFBUser = findViewById(R.id.txtFBUser);
        //https://stackoverflow.com/questions/23674131/android-facebook-integration-with-invalid-key-hash
        /*String packageName = getPackageName();
        Log.d("KeyHash:", "packageName is "+packageName);
        try {
            PackageInfo info = getPackageManager().getPackageInfo(
                    packageName,
                    PackageManager.GET_SIGNATURES);
            for (Signature signature : info.signatures) {
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(signature.toByteArray());
                Log.d("KeyHash:", Base64.encodeToString(md.digest(), Base64.DEFAULT));
            }
        }
        catch (PackageManager.NameNotFoundException e) {
            Log.d("KeyHash:", "PackageManager.NameNotFoundException");
        }
        catch (NoSuchAlgorithmException e) {
            Log.d("KeyHash:", "NoSuchAlgorithmException");
        }
*/
        Profile fbProfile = Profile.getCurrentProfile();
        if (fbProfile == null) {
            Log.d(logTag, "NOT logged in");
        }
        else
        {
            fbProfile.getName();
            Log.d(logTag, "fbProfile.getName() is " + fbProfile.getName());
        }
        AccessToken accessToken = AccessToken.getCurrentAccessToken();
        boolean isLoggedIn = accessToken != null && !accessToken.isExpired();
        //LoginManager.getInstance().logInWithReadPermissions(this, Arrays.asList("public_profile"));
        if(isLoggedIn)
        {
            extractUSerInfo(accessToken);
        }
        else {
            callbackManager = CallbackManager.Factory.create();
            LoginManager.getInstance().registerCallback(callbackManager,
                    new FacebookCallback<LoginResult>() {
                        @Override
                        public void onSuccess(LoginResult loginResult) {
                            // App code
                            Log.d("FB_LOGIN_TEST", "Logged in to FB");
                            // App code
                            extractUSerInfo(loginResult.getAccessToken());
                        }
                        @Override
                        public void onCancel() {
                            // App code
                            Log.d("FB_LOGIN_TEST", "FB Login cancelled");
                            txtFBUser.setText("Not Logged In");
                        }
                        @Override
                        public void onError(FacebookException exception) {
                            // App code
                            Log.d("FB_LOGIN_TEST", "Error while loging into FB");
                        }
                    });
            loginButton = (LoginButton) findViewById(R.id.login_button);
            //loginButton.setReadPermissions(Arrays.asList(EMAIL));
            loginButton.setPermissions(Arrays.asList("public_profile", "email", "user_birthday", "user_friends"));
            // If you are using in a fragment, call loginButton.setFragment(this);
        }
    }
    protected void extractUSerInfo(AccessToken assessToken)
    {
        AccessTokenTracker accessTokenTracker = new AccessTokenTracker() {
            @Override
            protected void onCurrentAccessTokenChanged(AccessToken oldAccessToken,
                                                       AccessToken currentAccessToken) {
                if (currentAccessToken == null) {
                    //write your code here what to do when user logout
                    Log.d("FB_LOGIN_TEST", "FB Logged out");
                    txtFBUser.setText("Not Logged In");
                }
            }
        };
        GraphRequest request = GraphRequest.newMeRequest(
                assessToken/*loginResult.getAccessToken()*/,
                new GraphRequest.GraphJSONObjectCallback() {
                    @Override
                    public void onCompleted(JSONObject object, GraphResponse response) {
                        Log.v("FB_LOGIN_TEST", "Roken Request Response is " + response.toString());
                        try {
                            String Name = object.getString("name");
                            String FEmail = object.getString("email");
                            Log.v("Email = ", " " + FEmail);
                            Toast.makeText(getApplicationContext(), "Name " + Name, Toast.LENGTH_LONG).show();
                            txtFBUser.setText(Name);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                });
        Bundle parameters = new Bundle();
        parameters.putString("fields", "id,name,email,gender,birthday");
        request.setParameters(parameters);
        request.executeAsync();
        //https://stackoverflow.com/questions/14996868/android-facebook-get-user-access-token-on-successful-login
        GraphRequestAsyncTask graphRequestAsyncTask = new GraphRequest(
                assessToken /*login_result.getAccessToken()*/,
                //AccessToken.getCurrentAccessToken(),
                "/me/friends",
                null/*bundle*/,
                HttpMethod.GET,
                new GraphRequest.Callback() {
                    public void onCompleted(GraphResponse response) {
                        try {
                            JSONArray rawName = response.getJSONObject().getJSONArray("data");
                            Log.d(logTag,"rawName friendList"+String.valueOf(rawName));
                            AccessToken token = AccessToken.getCurrentAccessToken();
                            Log.d(logTag,"access token is: "+String.valueOf(token));
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }
        ).executeAsync();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         callbackManager.onActivityResult(requestCode, resultCode, data);
         super.onActivityResult(requestCode, resultCode, data);
    }
    protected void fbExpressLogin()
    {
        LoginManager.getInstance().retrieveLoginStatus(this, new LoginStatusCallback() {
            @Override
            public void onCompleted(AccessToken accessToken) {
                // User was previously logged in, can log them in directly here.
                // If this callback is called, a popup notification appears that says
                // "Logged in as <User Name>"
            }
            @Override
            public void onFailure() {
                // No access token could be retrieved for the user
            }
            @Override
            public void onError(Exception exception) {
                // An error occurred
            }
        });
    }
}