4

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.

Moyed Ansari
  • 8,436
  • 2
  • 36
  • 57
Kamran Khatti
  • 3,754
  • 1
  • 20
  • 31
  • As a possible better alternative, have you thought about implementing OpenID Connect? That would accomplish what you are looking for here without needing to alter the library. – Justin Cherniak Nov 08 '16 at 03:58

1 Answers1

3

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.

Community
  • 1
  • 1
arogachev
  • 33,150
  • 7
  • 114
  • 117
  • Thanks for your valuable answer, I have a workaround which I implemented and deliver the project, but now since I am intended to use this library in future hence I want a proper workaround your fourth option looks good but still I am not in hurry and looking for a better approach. – Kamran Khatti Oct 25 '16 at 12:45
  • Glad to help. Remember that if you found different solution, you can always answer your own question and share info about it, it can be useful for others too. – arogachev Oct 25 '16 at 12:48
  • The workaround is the same I modified the core files which I mentioned in my answer :) that was not a good approach thats why looking for a proper solution your answer is productive but still looking for better one, may be any other can come up with a better approach otherwise your fourth option is always open. :) – Kamran Khatti Oct 25 '16 at 12:53
  • I don't recommend to use it (especially for production), it's only acceptable as a quick hack and temporary solution. – arogachev Oct 25 '16 at 12:59
  • @KamranKhatti I found one more way to solve it (using Yi2 built-in feature called class map), probably you will find it useful. I added this to answer. – arogachev Oct 25 '16 at 13:35
  • Wow excellent that really works, but it has one disadvantage since we replicate AccessToken.php hence future update in this file won't be available in our copied file, is there any solution to only modify createAccessToken method? – Kamran Khatti Oct 25 '16 at 13:59
  • @KamranKhatti I think no except using a monkey patch. Keep in mind that it's recommended to test app features after updating dependencies versions. I also recommend to lock dependencies on specific version / commit. – arogachev Oct 25 '16 at 14:07
  • 1
    Well this approach is much better than money patch thanks for your precious time and a proper solution of this genuine bug of filsh oauth library. – Kamran Khatti Oct 25 '16 at 14:09
  • 2
    @KamranKhatti its not a bug. Its how Aouth2 should work.I doubt if PR will be accepted at all on this. Such croco-duck solutions ends up a mess to maintain when you have enough of them. I would say, follow standards whenever possible.. – Stefano Mtangoo Oct 25 '16 at 17:26
  • @StefanoMtangoo this is genuine problem in response user not only use the fields which Aouth2 returns this is obvious and should have been very open to add new fields instead of doing it by customizing core files, I would suggest fix it in your next release. – Kamran Khatti Oct 25 '16 at 18:36