From f68bc7220375578f3917b71d786f0b58c0cc68d3 Mon Sep 17 00:00:00 2001 From: Doeke Norg Date: Wed, 28 Apr 2021 19:55:38 +0200 Subject: [PATCH 1/5] More granular control over token validation. --- CHANGELOG.md | 3 ++- src/Guard.php | 25 +++++++++++++++++++++---- src/Sanctum.php | 10 ++++++++++ tests/GuardTest.php | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ab742b6..489793d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Release Notes ## [Unreleased](https://github.com/laravel/sanctum/compare/v2.10.0...2.x) - +### Added +- `Sanctum::$validateCallback` callback for more granular control over access token validation ## [v2.10.0 (2021-04-20)](https://github.com/laravel/sanctum/compare/v2.9.4...v2.10.0) diff --git a/src/Guard.php b/src/Guard.php index 2fc1346e..cba9832b 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -65,10 +65,7 @@ public function __invoke(Request $request) $accessToken = $model::findToken($token); - if (! $accessToken || - ($this->expiration && - $accessToken->created_at->lte(now()->subMinutes($this->expiration))) || - ! $this->hasValidProvider($accessToken->tokenable)) { + if (!$this->isValidAccessToken($accessToken)) { return; } @@ -107,4 +104,24 @@ protected function hasValidProvider($tokenable) return $tokenable instanceof $model; } + + /** + * Determine if the provided access token is valid. + * + * @param mixed $accessToken + * @return bool + */ + protected function isValidAccessToken($accessToken): bool + { + $is_valid = $accessToken && ( + ($this->expiration && $accessToken->created_at->gt(now()->subMinutes($this->expiration))) + || $this->hasValidProvider($accessToken->tokenable) + ); + + if (is_callable(Sanctum::$validateCallback)) { + $is_valid = (bool) (Sanctum::$validateCallback)($accessToken, $is_valid); + } + + return $is_valid; + } } diff --git a/src/Sanctum.php b/src/Sanctum.php index 5da1a6ea..f79530e0 100644 --- a/src/Sanctum.php +++ b/src/Sanctum.php @@ -20,6 +20,16 @@ class Sanctum */ public static $runsMigrations = true; + /** + * A callback that can add to the validation of the access token. + * Receives 2 parameters: + * - (object) The provided access token model. + * - (bool) Whether the guard deemed the access token valid. + * + * @var callable|null + */ + public static $validateCallback = null; + /** * Set the current user for the application with the given abilities. * diff --git a/tests/GuardTest.php b/tests/GuardTest.php index 3094c13c..4d10c794 100644 --- a/tests/GuardTest.php +++ b/tests/GuardTest.php @@ -12,6 +12,7 @@ use Laravel\Sanctum\Guard; use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\PersonalAccessToken; +use Laravel\Sanctum\Sanctum; use Laravel\Sanctum\SanctumServiceProvider; use Mockery; use Orchestra\Testbench\TestCase; @@ -230,6 +231,45 @@ public function test_authentication_is_successful_with_token_if_user_provider_is $this->assertInstanceOf(EloquentUserProvider::class, $requestGuard->getProvider()); } + public function test_authentication_fails_if_callback_returns_false() + { + $this->loadLaravelMigrations(['--database' => 'testbench']); + $this->artisan('migrate', ['--database' => 'testbench'])->run(); + + config(['auth.guards.sanctum.provider' => 'users']); + config(['auth.providers.users.model' => User::class]); + + $factory = $this->app->make(AuthFactory::class); + $requestGuard = $factory->guard('sanctum'); + + $request = Request::create('/', 'GET'); + $request->headers->set('Authorization', 'Bearer test'); + + $user = User::forceCreate([ + 'name' => 'Taylor Otwell', + 'email' => 'taylor@laravel.com', + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'remember_token' => Str::random(10), + ]); + + $token = PersonalAccessToken::forceCreate([ + 'tokenable_id' => $user->id, + 'tokenable_type' => get_class($user), + 'name' => 'Test', + 'token' => hash('sha256', 'test'), + ]); + + Sanctum::$validateCallback = function($accessToken, bool $is_valid) { + $this->assertInstanceOf(PersonalAccessToken::class, $accessToken); + $this->assertTrue($is_valid); + + return false; + }; + + $user = $requestGuard->setRequest($request)->user(); + $this->assertNull($user); + } + protected function getPackageProviders($app) { return [SanctumServiceProvider::class]; From 5d9bcde22ba131685b2fa6b83bb7609a3d005b1d Mon Sep 17 00:00:00 2001 From: Doeke Norg Date: Wed, 28 Apr 2021 20:31:17 +0200 Subject: [PATCH 2/5] Updated CHANGELOG to include PR url. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 489793d6..f8175c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased](https://github.com/laravel/sanctum/compare/v2.10.0...2.x) ### Added -- `Sanctum::$validateCallback` callback for more granular control over access token validation +- `Sanctum::$validateCallback` callback for more granular control over access token validation ([#275](https://github.com/laravel/sanctum/pull/275)) ## [v2.10.0 (2021-04-20)](https://github.com/laravel/sanctum/compare/v2.9.4...v2.10.0) From c7f40af310ec045b167e1d0eb65a6b8f0adc1a7f Mon Sep 17 00:00:00 2001 From: Doeke Norg Date: Wed, 28 Apr 2021 20:36:07 +0200 Subject: [PATCH 3/5] Style CI fixes. --- src/Guard.php | 2 +- tests/GuardTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index cba9832b..6b4abd10 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -65,7 +65,7 @@ public function __invoke(Request $request) $accessToken = $model::findToken($token); - if (!$this->isValidAccessToken($accessToken)) { + if (! $this->isValidAccessToken($accessToken)) { return; } diff --git a/tests/GuardTest.php b/tests/GuardTest.php index 4d10c794..8948ddeb 100644 --- a/tests/GuardTest.php +++ b/tests/GuardTest.php @@ -259,11 +259,11 @@ public function test_authentication_fails_if_callback_returns_false() 'token' => hash('sha256', 'test'), ]); - Sanctum::$validateCallback = function($accessToken, bool $is_valid) { - $this->assertInstanceOf(PersonalAccessToken::class, $accessToken); - $this->assertTrue($is_valid); + Sanctum::$validateCallback = function ($accessToken, bool $is_valid) { + $this->assertInstanceOf(PersonalAccessToken::class, $accessToken); + $this->assertTrue($is_valid); - return false; + return false; }; $user = $requestGuard->setRequest($request)->user(); From ed96922edc56e8d101574cf1b80e7fd1e271a2b9 Mon Sep 17 00:00:00 2001 From: Doeke Norg Date: Thu, 29 Apr 2021 10:20:31 +0200 Subject: [PATCH 4/5] Refactored `isValidAccessToken()` --- src/Guard.php | 11 +++++++---- src/Sanctum.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index 6b4abd10..ab7274f3 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -113,10 +113,13 @@ protected function hasValidProvider($tokenable) */ protected function isValidAccessToken($accessToken): bool { - $is_valid = $accessToken && ( - ($this->expiration && $accessToken->created_at->gt(now()->subMinutes($this->expiration))) - || $this->hasValidProvider($accessToken->tokenable) - ); + if (!$accessToken) { + return false; + } + + $is_valid = + (!$this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration))) + && $this->hasValidProvider($accessToken->tokenable); if (is_callable(Sanctum::$validateCallback)) { $is_valid = (bool) (Sanctum::$validateCallback)($accessToken, $is_valid); diff --git a/src/Sanctum.php b/src/Sanctum.php index f79530e0..104b79cd 100644 --- a/src/Sanctum.php +++ b/src/Sanctum.php @@ -28,7 +28,7 @@ class Sanctum * * @var callable|null */ - public static $validateCallback = null; + public static $validateCallback; /** * Set the current user for the application with the given abilities. From 7c84f1bde6dba6aa8329c605e96726078934f399 Mon Sep 17 00:00:00 2001 From: Doeke Norg Date: Thu, 29 Apr 2021 10:21:45 +0200 Subject: [PATCH 5/5] Style CI updates --- src/Guard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Guard.php b/src/Guard.php index ab7274f3..f360060d 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -113,12 +113,12 @@ protected function hasValidProvider($tokenable) */ protected function isValidAccessToken($accessToken): bool { - if (!$accessToken) { + if (! $accessToken) { return false; } $is_valid = - (!$this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration))) + (! $this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration))) && $this->hasValidProvider($accessToken->tokenable); if (is_callable(Sanctum::$validateCallback)) {