Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Make tokenRepository replaceable #1288

Closed
wants to merge 5 commits into from
Closed

[10.x] Make tokenRepository replaceable #1288

wants to merge 5 commits into from

Conversation

rust17
Copy link

@rust17 rust17 commented May 27, 2020

This pr make the TokenRepository can be replace by custom tokenRepository. By adding Passport::useTokenRepository(new CustomTokenRepository) at App\Providers\AuthServiceProvider, it can be easily use cache or other database driver to manage the tokens. And also make it possible to solve problems like #347 #382

@@ -95,7 +97,7 @@ public function revokeAccessToken($id)
* @param string $id
* @return bool
*/
public function isAccessTokenRevoked($id)
public function isAccessTokenRevoked($id): bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove all of these return types that were added.

* @return \Laravel\Passport\Contracts\TokenRepository|void
*/
public static function useTokenRepository(
Contracts\TokenRepository $tokenRepository = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import the full namespace and alias it as TokenRepositoryInterface.

) {
if (is_null($tokenRepository)) {
return static::$tokenRepository = new TokenRepository();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would only allow this method to set a token repository and then register the base one in the service provider.

@@ -91,6 +91,7 @@ public function register()
$this->mergeConfigFrom(__DIR__.'/../config/passport.php', 'passport');

Passport::setClientUuids($this->app->make(Config::class)->get('passport.client_uuids', false));
Passport::useTokenRepository();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Register the token repository instead of using a static method. Place the registration in a separate method like the methods below.

* @param string $id
* @return bool
*/
public function isAccessTokenRevoked($id): bool;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all of the return types on these methods.

@driesvints driesvints changed the title make tokenRepository replaceable [10.x] Make tokenRepository replaceable May 28, 2020
@driesvints
Copy link
Member

You're saying this will solve #382 but I don't see how? Can you explain further?

Also: why do you want to use a different database driver?

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If possible, please consider releasing your code as a package so that the community can still take advantage of your contributions!

If you feel absolutely certain that this code corrects a bug in the framework, please "@" mention me in a follow-up comment with further explanation so that GitHub will send me a notification of your response.

@rust17
Copy link
Author

rust17 commented May 29, 2020

@driesvints Thanks for your recommends. And sorry for the unclear explanation. I want to write a demo token repository to explain why. It's just a demo and may have bugs, but it can help me explain how to solve #382.

<?php

namespace App\Repositories;

use Illuminate\Database\Eloquent\Collection;
use Laravel\Passport\Contracts\TokenRepository;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
use Cache;

class CacheTokenRepository implements TokenRepository
{
    protected $cache_key = 'passport_token_';

    /**
     * Creates a new Access Token.
     *
     * @param  array                   $attributes
     * @return \Laravel\Passport\Token
     */
    public function create($attributes): Token
    {
        return Passport::token()->create($attributes);
    }

    /**
     * Get a token by the given ID.
     *
     * @param  string                  $id
     * @return \Laravel\Passport\Token
     */
    public function find($id): Token
    {
        return Cache::remember($this->cache_key.$id, now()->addMinutes(30), function() use ($id) {
            return Passport::token()->where('id', $id)->first();
        });
    }

    /**
     * Get a token by the given user ID and token ID.
     *
     * @param  string                       $id
     * @param  int                          $userId
     * @return \Laravel\Passport\Token|null
     */
    public function findForUser($id, $userId)
    {
        return Cache::remember($this->cache_key.$id, now()->addMinutes(30), function() use ($id, $userId) {
            return Passport::token()->where('id', $id)->where('user_id', $userId)->first();
        });
    }

    /**
     * Get the token instances for the given user ID.
     *
     * @param  mixed                                    $userId
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function forUser($userId): Collection
    {
        return Cache::remember($this->cache_key.$userId, now()->addMinutes(30), function() use ($userId) {
            return Passport::token()->where('user_id', $userId)->get();
        });
    }

    /**
     * Get a valid token instance for the given user and client.
     *
     * @param  \Illuminate\Database\Eloquent\Model $user
     * @param  \Laravel\Passport\Client            $client
     * @return \Laravel\Passport\Token|null
     */
    public function getValidToken($user, $client)
    {
        return Cache::remember($this->cache_key.$user->getKey(), now()->addMinutes(30), function() use ($client, $user) {
            return $client->tokens()
                    ->whereUserId($user->getKey())
                    ->where('revoked', 0)
                    ->where('expires_at', '>', Carbon::now())
                    ->first();
        });
    }

    /**
     * Store the given token instance.
     *
     * @param  \Laravel\Passport\Token $token
     * @return void
     */
    public function save(Token $token): void
    {
        $token->save();
    }

    /**
     * Revoke an access token.
     *
     * @param  string $id
     * @return mixed
     */
    public function revokeAccessToken($id)
    {
        return Passport::token()->where('id', $id)->update(['revoked' => true]);
    }

    /**
     * Check if the access token has been revoked.
     *
     * @param  string $id
     * @return bool
     */
    public function isAccessTokenRevoked($id): bool
    {
        if ($token = $this->find($id))
        {
            return $token->revoked;
        }

        return true;
    }

    /**
     * Find a valid token for the given user and client.
     *
     * @param  \Illuminate\Database\Eloquent\Model $user
     * @param  \Laravel\Passport\Client            $client
     * @return \Laravel\Passport\Token|null
     */
    public function findValidToken($user, $client)
    {
        return $client->tokens()
                      ->whereUserId($user->getKey())
                      ->where('revoked', 0)
                      ->where('expires_at', '>', Carbon::now())
                      ->latest('expires_at')
                      ->first();
    }
}

<?php

namespace App\Providers;
...
use App\Repositories\CacheTokenRepository;
use Laravel\Passport\Contracts\TokenRepository;
...
class AuthServiceProvider extends ServiceProvider
{
...
    public function boot()
    {
        ...
        $this->app->singleton(TokenRepository::class, CacheTokenRepository::class);
    }
}

In my custom tokenRepository, tokens are still store in database. I add a layer of cache. Here's the result:

  • before
    image
  • after
    image

Also, in my latest commit, I did the change as you requested. Thanks again.

@driesvints
Copy link
Member

You can simply swap out the token repository in your service provider:

$this->app->singleton(TokenRepository::class, function () {
    return /* your custom token repository */;
});

@rust17
Copy link
Author

rust17 commented May 29, 2020

You can simply swap out the token repository in your service provider:

$this->app->singleton(TokenRepository::class, function () {
    return /* your custom token repository */;
});

I totally forgot this way. That's what I need. Thanks !

@sepisoltani
Copy link

@rust17
How about expiring the cache explicitly when the user logs out ?

@sepisoltani
Copy link

sepisoltani commented Oct 12, 2020

@rust17 @driesvints @taylorotwell

TokenRepository is a class . not an interface
I think we can create a class that extends TokenRepository and override the methods where we need cache.

<?php


namespace App\Repositories\Passport;

use Illuminate\Support\Facades\Cache;
use Laravel\Passport\TokenRepository;


class CacheTokenRepository extends TokenRepository
{

    protected string $cache_key = 'passport-token';

    public function find($id)
    {
        return Cache::remember("$this->cache_key-id-$id", now()->addMinutes(30), function () use ($id) {
            return parent::find($id);
        });
    }

    public function findForUser($id, $userId)
    {
        return Cache::remember("$this->cache_key-id-$id-user-$userId", now()->addMinutes(30), function () use ($id, $userId) {
            return parent::findForUser($id, $userId);
        });
    }

    public function forUser($userId)
    {
        return Cache::remember("$this->cache_key-user-$userId", now()->addMinutes(30), function () use ($userId) {
            return parent::forUser($userId);
        });
    }


}

then we can bind this class in AuthServiceProvider or in another service provider

 $this->app->bind(TokenRepository::class, function () {
            return new CacheTokenRepository();
        });

@rust17
Copy link
Author

rust17 commented Oct 17, 2020

@sepisoltani found a better solution. You can take a look at this repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants