Search code examples
phpfacebookfacebook-graph-apifacebook-php-sdk

Facebook SDK error: Cross-site request forgery validation failed. Required param "state" missing from persistent data


I recently upgraded to the latest version of the facebook SDK and I'm having issues logging users in. I generate the login link just fine, but when facebook sends the user back to my site with the token, I get this error:

fb sdk error: Cross-site request forgery validation failed. Required param "state" missing from persistent data.

I tried to do some trouble shooting. I printed out everything in the session data and everything in the GET request. I see that the GET has a state parameter and the session data has a FBRLH_state parameter. They both have the same value. So how is it telling me that the parameter is missing?

I've tried some suggestions I've seen on other questions (ie, starting the session), but nothing seems to work.

Any help would be greatly appreciated! I'm using the php-graph-sdk-5.5. My facebook connect file is below

    if(!class_exists('facebook')){
    class facebook{

        private $db = null;
        private $fb = null;
        private $token = null;
        private $DEV = null;
        private $sdk_error = null;
        private $api_error = null;
        private $verbose = false;
        private $graph_user = null;
        private $db_helper = null;
        private $errors = null;

        public function __construct($db,
                                    $fb_id = FB_APP_ID,
                                    $fb_secret = FB_APP_SECRET,
                                    $fb_version = FB_DEFAULT_GRAPH_VERSION){
            if($this->verbose) echo '<pre>';
            if($this->verbose) echo 'starting construction'.PHP_EOL;
            $this->db = $db;
            if(!$this->fb){
                $this->log[] = 'no connect found. building..'.PHP_EOL;

                $this->fb = new Facebook\Facebook(array(
                            'app_id' => $fb_id,
                            'app_secret' => $fb_secret,

                            'default_graph_version' => $fb_version));
                if(!$this->fb){
                    die('facebook initialization failure');
                }
                $this->log[] = 'finished building new connection'.PHP_EOL;
            }
        }

        public function get_login_url($callback_uri, $permissions = ['email','user_birthday']){

            global $_DEV,$_config;
            $helper = $this->fb->getRedirectLoginHelper();

            $callback_host = ($_DEV ? $_config['dev_domain'] : $_config['live_domain']);
            $callback_url = 'https://'.$callback_host.$callback_uri;
            return $helper->getLoginUrl($callback_url, $permissions);
        }

        public function catch_token(){
            if($this->token){
                $this->log[] = 'already have token.'.PHP_EOL;

                return $this->token;
            } else if(!$this->fb){
                $this->log[] = $this->error[] = 'no facebook connection in catch token()';

            }

            $this->log[] = 'starting catch token routine.'.PHP_EOL;
            //$_SESSION['state']=$_GET['state'];
            echo '<pre>' . var_export($_SESSION, true) . '</pre>';
                        echo '<BR><BR><pre>' . var_export($_GET, true) . '</pre>';
                $helper = $this->fb->getRedirectLoginHelper();

                $this->token = $helper->getAccessToken();

                $this->log[] = 'caught token: '.$this->token;
                $string_token = $this->token.PHP_EOL;
                //die($string_token);
            try {

                $helper = $this->fb->getRedirectLoginHelper();

                $this->token = $helper->getAccessToken();

                $this->log[] = 'caught token: '.$this->token;
                $string_token = $this->token.PHP_EOL;

                return $this->user_flush();
            } catch(Facebook\Exceptions\FacebookResponseException $e) {
                // When Graph returns an error
                $this->log[] = $this->errors[] = 'fb api error: ' . $e->getMessage();
                return null;
            } catch(Facebook\Exceptions\FacebookSDKException $e) {
                // When validation fails or other local issues
                $this->log[] = $this->errors[] = 'fb sdk error: ' . $e->getMessage();
                return null;
            } catch(Exception $e){
                $this->log[] = $this->errors[] = 'unknown error: '.$e->getMessage();
                return null;
            }
        }

        public function get_token(){
            $this->log[] = 'get token called.'.PHP_EOL;
            if($this->token){
                $this->log[] = 'token found in object'.PHP_EOL;
                //echo '<pre>';
                //die(debug_print_backtrace());
                return $this->token;
            } else {
                $this->log[] = $this->errors[] = 'token not found in object.'.PHP_EOL;
                return null;
            }
        }

        public function get_user($override = false){
            $fields = array(
                'first_name',
                'last_name',
                'email',
                'id',
                'picture',
                'birthday',
                'gender',);
            $fields = implode(',',$fields);
            if($this->graph_user === null){
                if($this->fb && $this->get_token()){
                    try {
                      // Returns a Facebook\FacebookResponse object
                      $resp_url = '/me?fields='.$fields.'&debug=all';
                      $this->log[] = $resp_url;
                      $response = $this->fb->get($resp_url, $this->get_token());
                      $this->graph_user = $response->getGraphUser();
                      return $this->graph_user;
                    } 
                    catch(Facebook\Exceptions\FacebookResponseException $e) {
                        // When Graph returns an error
                        $this->api_error = 'fb api error: ' . $e->getMessage();
                        $this->errors[] = $this->api_error;
                        return null;
                    }
                    catch(Facebook\Exceptions\FacebookSDKException $e) {
                        // When validation fails or other local issues
                        $this->sdk_error = 'fb sdk error: ' . $e->getMessage();
                        $this->errors[] = $this->sdk_error;
                        return null;
                    }
                } else {
                    $this->sdk_error = "get_user(): fb connection or token not set. are you logged in?";
                    $this->errors[] = $this->sdk_error;
                    //echo '<pre>';
                    //debug_print_backtrace();
                    //die('token: '.$this->token);
                    return null;
                }
            } else {
                $this->sdk_error = "get_user(): graph_user already set";
                $this->errors[] = $this->sdk_error;
                return $this->graph_user;
            }

        }

        public function get_user_first_name(){
            return $this->get_user()['first_name'];
        }
        public function get_user_last_name(){
            return $this->get_user()['last_name'];
        }
        public function get_user_id(){
            return $this->get_user()['id'];
        }
        public function get_user_email(){
            return $this->get_user()['email'];
        }
        public function get_user_picture(){
            return $this->get_user()['picture']['url'];
        }
        public function get_user_birthday(){
            return $this->get_user()['birthday'];
        }

        public function user_flush(){
            //this is the command function.
            //  runs the basic functionality of this class
            //  by adding this user to the database if they're not there
            //      and logging them in if they are.
            $this->graph_user = $this->get_user();
            //$this->log['graph_user_at_user_flush'] = $this->graph_user;
            $this->build_user();
            $this->log['GRAPH_USER'] = $this->get_user();
            $this->log['user_input_array@user_flush'] = $this->user_input;
            if($return = $this->user->fb_register()){
                //die(print_r(debug_backtrace(),true));
                //$this->log['success return'] = '. '.$return;
                return $return;
            } else {
                //die('<pre>'.print_r(debug_backtrace(),true));
                $this->log['fb_register_fail'] = array('fb_register() (also login) failed.',$this->user->get_errors());
                return null;
            }
        }

        public function build_user(){

            $this->user_input['first_name'] = $this->get_user_first_name();
            //$this->user_input['last_name'] = $this->get_user_last_name();
            $this->user_input['facebook_id'] = $this->get_user_id();
            $this->user_input['email'] = $this->get_user_email();
            $this->user_input['image_url'] = $this->get_user_picture();
            $this->user_input['birthday'] = $this->get_user_birthday();
            if($this->verbose) 
                print_r($this->user_input);
            $this->user = new user($this->user_input,$this->db);
        }

        public function logout(){
            unset($_SESSION['fb_id']);
            unset($this->token);
            unset($this->fb);
        }

        public function get_errors(){
            return array_unique($this->errors);
        }
        public function get_log(){
            return array_unique($this->log);
        }
    }
}


//finally, create the connection.
if(!isset($fb))
    $fb = new facebook($db);

Solution

  • fb sdk error: Cross-site request forgery validation failed. Required param "state" missing from persistent data.

    It has something to do with that you are going through the routine of calling getRedirectLoginHelper and $helper->getAccessToken() twice - once "on their own", and then again inside a try-catch block (copy&paste mistake, or unfortunate debug attempt maybe?)

    I'm a bit too lazy to go check the SDK source right now, but I think it deliberately unsets the state parameter inside the session after the code was exchanged for a token, as part of making the whole process more secure - so that when you call getAccessToken a second time, it fails.