Kamran Khatti Kamran Khatti - 1 month ago 45
reST (reStructuredText) Question

How to override default Yii2 - Filsh Oauth Server response

I am using Yii2 Filsh Oauth Server which is working fine,however when I login it generates AccessToken with its default fields i.e

{
"access_token": "f3389e81c234276967079b2293795fc9104a2fac",
"expires_in": 86400,
"token_type": "Bearer",
"user_id": 9,
"scope": null,
"refresh_token": "851464a210f56bb831da378a43e1016bd3e765d7",
}


But I needed to add User info in its response something like

{
"access_token": "f3389e81c234276967079b2293795fc9104a2fac",
"expires_in": 86400,
"token_type": "Bearer",
"scope": null,
"refresh_token": "851464a210f56bb831da378a43e1016bd3e765d7",
"user": {
"id": 9,
"first_name": "Test",
"last_name": "Test2",
"username": "test",
"email": "test@gmail.com",
"status": 1,
"dob": "20-08-1990",
"gender": "Male",
}
}


I came up with a weird workaround I customized bshaffer core library file (which is not a good approach) to meet my requirements, what I did, I changed this line in User model:

return ['user_id' => $user->getId()];


TO THIS

return ['user_id' => [$user->getId(), $userObject]]; //I get all user info in $userObject and passed an array with two fields


since I am passing an array instead of single
$user->getId()
hence I needed to modify bshaffer library file AccessToken.php which is available at this path:
vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php
on line 76

I CHANGED THIS:

public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
$token = array(
"access_token" => $this->generateAccessToken(),
"expires_in" => $this->config['access_lifetime'],
"token_type" => $this->config['token_type'],
"user_id" => $user_id,
"scope" => $scope
);

$this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id, $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);

if ($includeRefreshToken && $this->refreshStorage) {
$token["refresh_token"] = $this->generateRefreshToken();
$expires = 0;
if ($this->config['refresh_token_lifetime'] > 0) {
$expires = time() + $this->config['refresh_token_lifetime'];
}
$this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id, $expires, $scope);
}
return $token;
}


TO THIS:

public function createAccessToken($client_id, $user_id, $scope = null, $includeRefreshToken = true)
{
$token = array(
"access_token" => $this->generateAccessToken(),
"expires_in" => $this->config['access_lifetime'],
"token_type" => $this->config['token_type'],
"scope" => $scope,
"user" => $user_id[1] //NOTE: I added new user field and passed second index of array which is user node
);

//NOTE: Here I passed $user_id[0] since $user_id is array hence I am using its 0 index here which has id
$this->tokenStorage->setAccessToken($token["access_token"], $client_id, $user_id[0], $this->config['access_lifetime'] ? time() + $this->config['access_lifetime'] : null, $scope);

if ($includeRefreshToken && $this->refreshStorage) {
$token["refresh_token"] = $this->generateRefreshToken();
$expires = 0;
if ($this->config['refresh_token_lifetime'] > 0) {
$expires = time() + $this->config['refresh_token_lifetime'];
}
//NOTE: Same goes here passing $user_id[0]
$this->refreshStorage->setRefreshToken($token['refresh_token'], $client_id, $user_id[0], $expires, $scope);
}

return $token;

}


Everything works pefect now the PROBLEM is since I modified bshaffer core file when I run composer it again overrites its default code and my changes just wash out each time after running composer I again need to modify same file. I need a proper workarount may be any
component
where I override this calss/method and put my changes so that it ramians same after running composer.

Answer

Obviously you should not modify anything under vendor folder because such changes will be overridden on next composer update.

As far as I can see there is no option to configure that, and what makes it harder is a fact that it's a problem of extension's dependent library and not extension itself. At first try to find a way to configure that or add workaround without need to make such changes. If you can't the possible options are:

1) Create issue / pull request and wait for changes being added in bshaffer/oauth2-server-php. Make sure Filsh/yii2-oauth2-server has the correct dependency so it allows update to the newer version of bshaffer/oauth2-server-php (one more issue / pull request for Filsh/yii2-oauth2-server will be probably required in this case). This can take a pretty long time.

2) Create forks for both libraries, make desired changes and use them instead. You can use repositories section in composer.json if you don't want to publish it on Packagist. See more info in official Composer docs.

3) Copy both extensions to your project code and put under version control, modify them and use instead of those that in vendor folder.

4) Add monkey patch. Since this feature is not supported by PHP natively, you can use extension which provides such functionality - antecedent/patchwork.

In this case you just can replace this method with your own.

Besides examples in official docs, I found article that can help too and have example with classes. In your case it's simpler and will be something like that:

replace(\OAuth2\ResponseType:AccessToken:class. '::createAccessToken', function ($client_id, $user_id, $scope = null, $includeRefreshToken = true) {
    // Redefine method behavior as you want here
});

Option 1 can take a long time and there is always a chance that your issue or pull request will be rejected. With options 2 and 3 you lose the possibility for updates.

If you:

  • can't find a way of configuring it or workaround without need to make such changes;

  • need this urgently;

  • feel that it looks like project specific feature rather than common one.

use option 4, it's probably the best one in this case.

UPDATE:

Here is another one option using Yii2 built-in feature.

5) You can use Yii::$classMap() for replacing class with your custom one. I tested it and it works well with extension classes.

Copy vendor/bshaffer/oauth2-server-php/src/OAuth2/ResponseType/AccessToken.php and paste it let's say in common/components folder. Modify createAccessToken method, but don't modify the namespace (otherwise you will get Class not found error).

Switch the class using class map during application bootstrap:

Yii::$classMap['OAuth2\ResponseType\AccessToken'] = '@common/components/AccessToken.php';

You can place it in index.php right before application is initialized and run:

$application = new yii\web\Application($config);
$application->run();

However I recommend to create separate bootstrap component implementing BootstrapInterface and include it in application config.

Now when referring to OAuth2\ResponseType\AccessToken your custom class will be used. The disadvantage is you can't extend from extension class and override just one method, but that's because you need to keep original namespace.

Check this related SO answer.

Comments