phpyii2yii2-authclient

How save access_token to db using yii2-dektrium facebook login?


I'm using yii2-dektrium to allow users login with their facebook's accounts.

After the login is done, I need to make API request from my server to get data of the user's accounts. One example of request is:

$client = Yii::$app->authClientCollection->getClient('facebook');
$response = $client->createApiRequest()
        ->setMethod('GET')
        ->setUrl('v2.12/me/accounts')
        ->send();

The access_token is saved on session so I need to persist it to the database.

I already added a column access_token to the social_account default table of yii2-dektrium but I don't know how to get and save it, and further more, how to apply it to the requests.

After reading for a while. I think the way to save it is overriding the method connect in dektrium\user\controllers\SecurityController.

public function connect(ClientInterface $client)
{
    /** @var Account $account */
    $account = \Yii::createObject(Account::className());
    $event   = $this->getAuthEvent($account, $client);
    $this->trigger(self::EVENT_BEFORE_CONNECT, $event);
    $account->connectWithUser($client);
    $this->trigger(self::EVENT_AFTER_CONNECT, $event);
    $this->action->successUrl = Url::to(['/user/settings/networks']);
}

And for applying to the request, override applyAccessTokenToRequest on yii\authclient\clients\Facebook

public function applyAccessTokenToRequest($request, $accessToken)
{
    parent::applyAccessTokenToRequest($request, $accessToken);
    $data = $request->getData();
    if (($machineId = $accessToken->getParam('machine_id')) !== null) {
        $data['machine_id'] = $machineId;
    }
    $data['appsecret_proof'] = hash_hmac('sha256', $accessToken->getToken(), $this->clientSecret);
    $request->setData($data);
}

I can't get it done. And I'm not sure if it is the right way to do it. What I'm missing?


Solution

  • For save the access_token the first time you have to overwrite the connect action from \dektrium\user\controllers\SecurityController.

    class SecurityController extends \dektrium\user\controllers\SecurityController
    {
        public function connect(ClientInterface $client)
        {
            // default implementation of connect
            $account = \Yii::createObject(Account::className());
            $event   = $this->getAuthEvent($account, $client);
            $this->trigger(self::EVENT_BEFORE_CONNECT, $event);
            $account->connectWithUser($client);
            $this->trigger(self::EVENT_AFTER_CONNECT, $event);
    
            // get acess_token from $client
            $access_token['tokenParamKey'] = $client->getAccessToken()->tokenParamKey;
            $access_token['tokenSecretParamKey'] = $client->getAccessToken()->tokenSecretParamKey;
            $access_token['createTimestamp'] = $client->getAccessToken()->createTimestamp;
            $access_token['_expireDurationParamKey'] = $client->getAccessToken()->getExpireDurationParamKey();
            $access_token['_params'] = $client->getAccessToken()->getParams();
    
            // save acess_token to social_account table
            $model = SocialAccount::find()->where(['provider' => $client->getName()])->andWhere(['user_id' => Yii::$app->user->id])->one();
            $model->access_token = \yii\helpers\Json::encode($access_token);
            $model->save(false);
    
            $this->action->successUrl = Url::to(['/user/settings/networks']);
        }
    }
    

    To get the access_token store in the database for further API Requests create a class that extends yii\authclient\SessionStateStorage and overwrite get method.

    namespace app\models\authclient;
    
    class DbStateStorage extends SessionStateStorage
    {
        public function get($key)
        {
            // $key is a complex string that ends with 'token' if the value to get is the actual access_token
            $part = explode('_', $key);
            if (count($part) == 3 && $part[2] == 'token') {
                $account = SocialAccount::find()
                    ->where(['provider' => $part[1]])
                    ->andWhere(['user_id' => Yii::$app->user->id])
                    ->one();
                if ($account != null) {
                    $access_token = json_decode($account->access_token);
                    $token = new \yii\authclient\OAuthToken();
                    $token->createTimestamp = $access_token->createTimestamp;
                    $token->tokenParamKey = $access_token->tokenParamKey;
                    $token->tokenSecretParamKey = $access_token->tokenSecretParamKey;
                    $token->setParams((array)$access_token->_params);
                    $token->setExpireDurationParamKey($access_token->_expireDurationParamKey);
                    return $token;
                }
            }
            if ($this->session !== null) {
                return $this->session->get($key);
            }
            return null;
        }
    }
    

    Finally set the DbStateStorage to your authclient

    class Facebook extends \dektrium\user\clients\Facebook
    {
        public function __construct()
        {
            $this->setStateStorage('app\models\authclient\DbStateStorage');
        }
    }