From 2a747789496fb37179c9579b4a4c8e2bd66a025d Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 22 Aug 2023 15:48:36 +0800 Subject: [PATCH 01/28] wip Signed-off-by: Mior Muhammad Zaki --- .gitattributes | 1 + composer.json | 4 +- workbench/app/Models/User.php | 42 +++++++++++++++++++ .../factories/PersonalAccessTokenFactory.php | 37 ++++++++++++++++ workbench/database/factories/UserFactory.php | 21 ++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 workbench/app/Models/User.php create mode 100644 workbench/database/factories/PersonalAccessTokenFactory.php create mode 100644 workbench/database/factories/UserFactory.php diff --git a/.gitattributes b/.gitattributes index 6c56d0f0..2227e2c7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ /.github export-ignore /art export-ignore /tests export-ignore +/workbench export-ignore .editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore diff --git a/composer.json b/composer.json index a1a2ebc3..a117949d 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,9 @@ }, "autoload-dev": { "psr-4": { - "Laravel\\Sanctum\\Tests\\": "tests/" + "Laravel\\Sanctum\\Tests\\": "tests/", + "Workbench\\App\\": "workbench/app/", + "Workbench\\Database\\Factories\\": "workbench/database/factories/" } }, "extra": { diff --git a/workbench/app/Models/User.php b/workbench/app/Models/User.php new file mode 100644 index 00000000..c4c84c3f --- /dev/null +++ b/workbench/app/Models/User.php @@ -0,0 +1,42 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + ]; +} diff --git a/workbench/database/factories/PersonalAccessTokenFactory.php b/workbench/database/factories/PersonalAccessTokenFactory.php new file mode 100644 index 00000000..7ce39b60 --- /dev/null +++ b/workbench/database/factories/PersonalAccessTokenFactory.php @@ -0,0 +1,37 @@ + + */ +class PersonalAccessTokenFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string<\Illuminate\Database\Eloquent\Model|TModel> + */ + protected $model = PersonalAccessToken::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->company(), + 'token' => hash('sha256', 'test'), + 'created_at' => Carbon::now(), + 'expires_at' => null, + ]; + } +} diff --git a/workbench/database/factories/UserFactory.php b/workbench/database/factories/UserFactory.php new file mode 100644 index 00000000..af134aaa --- /dev/null +++ b/workbench/database/factories/UserFactory.php @@ -0,0 +1,21 @@ + + */ + public function modelName() + { + return User::class; + } +} From 2d2df473ce517597eb3b1119870b8ecfe23c4f58 Mon Sep 17 00:00:00 2001 From: Patrick O'Meara Date: Tue, 22 Aug 2023 15:49:13 +0800 Subject: [PATCH 02/28] Ensure device has not been logged out This adds a middleware to check that the password has in session is the same as the current users password. This fixes a security issue where an attacker can keep sending requests to an API using the sanctum auth after the password has been changed. Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotBeenLoggedOut.php | 42 +++++++++++++++++++ .../EnsureFrontendRequestsAreStateful.php | 1 + 2 files changed, 43 insertions(+) create mode 100644 src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php new file mode 100644 index 00000000..e9469e26 --- /dev/null +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -0,0 +1,42 @@ +hasSession() + && $request->user() + && $request->session()->has($key = 'password_hash_'.$this->auth->getDefaultDriver()) + && $request->session()->get($key) !== $request->user()->getAuthPassword()) { + $this->logout($request); + + throw new AuthenticationException('Unauthenticated.', [$this->auth->getDefaultDriver()]); + } + + return $next($request); + } + + protected function logout(Request $request) + { + $this->auth->logoutCurrentDevice(); + + $request->session()->flush(); + } +} diff --git a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php index 031c29ff..2291bd1d 100644 --- a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php +++ b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php @@ -52,6 +52,7 @@ protected function frontendMiddleware() \Illuminate\Session\Middleware\StartSession::class, config('sanctum.middleware.validate_csrf_token'), config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class), + config('sanctum.middleware.ensure_not_logged_out', EnsureDeviceHasNotBeenLoggedOut::class), ]))); array_unshift($middleware, function ($request, $next) { From b3b0486fe94ea2c1d1e2a7ba8eca94e24561ed79 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 22 Aug 2023 15:51:10 +0800 Subject: [PATCH 03/28] wip Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotLoggedOutTest.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php new file mode 100644 index 00000000..87dc072d --- /dev/null +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -0,0 +1,87 @@ +set([ + 'app.key' => 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF', + 'auth.guards.sanctum.provider' => 'users', + 'auth.providers.users.model' => User::class, + 'database.default' => 'testing', + ]); + } + + protected function defineRoutes($router) + { + $router->get('/sanctum/api/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->name; + })->middleware('auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); + + $router->get('/sanctum/web/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->name; + })->middleware('web', 'auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); + } + + public function test_middleware_can_authorize_valid_user_using_header() + { + $token = PersonalAccessTokenFactory::new()->for( + $user = UserFactory::new()->create(), 'tokenable') + ->create(); + + $this->getJson('/sanctum/api/user', [ + 'Authorization' => 'Bearer test', + ])->assertOk() + ->assertSee($user->name); + } + + public function test_middleware_can_authorize_valid_user_using_acting_as() + { + $token = PersonalAccessTokenFactory::new()->for( + $user = UserFactory::new()->create(), 'tokenable') + ->create(); + + Sanctum::actingAs($user); + + $this->getJson('/sanctum/web/user') + ->assertOk() + ->assertSee($user->name); + } + + public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change() + { + $token = PersonalAccessTokenFactory::new()->for( + $user = UserFactory::new()->create(), 'tokenable') + ->create(); + + Sanctum::actingAs($user); + + $this->getJson('/sanctum/web/user') + ->assertOk() + ->assertSee($user->name); + + $user->password = bcrypt('laravel'); + $user->save(); + + $this->getJson('/sanctum/web/user') + ->assertStatus(401); + } +} From fbf7b547f70303ef31f11ddef190579e2e7abdd6 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 22 Aug 2023 15:55:47 +0800 Subject: [PATCH 04/28] wip Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotBeenLoggedOut.php | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index e9469e26..0e4acb33 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -4,39 +4,91 @@ use Closure; use Illuminate\Auth\AuthenticationException; +use Illuminate\Auth\SessionGuard; use Illuminate\Contracts\Auth\Factory as AuthFactory; use Illuminate\Http\Request; +use Illuminate\Support\Arr; use Symfony\Component\HttpFoundation\Response; class EnsureDeviceHasNotBeenLoggedOut { - public function __construct(protected AuthFactory $auth) + /** + * The authentication factory implementation. + * + * @var \Illuminate\Contracts\Auth\Factory + */ + protected $auth; + + /** + * Create a new middleware instance. + * + * @param \Illuminate\Contracts\Auth\Factory $auth + * @return void + */ + public function __construct(AuthFactory $auth) { + $this->auth = $auth; } /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + * + * @throws \Illuminate\Auth\AuthenticationException */ public function handle(Request $request, Closure $next): Response { - if ($request->hasSession() - && $request->user() - && $request->session()->has($key = 'password_hash_'.$this->auth->getDefaultDriver()) - && $request->session()->get($key) !== $request->user()->getAuthPassword()) { - $this->logout($request); - - throw new AuthenticationException('Unauthenticated.', [$this->auth->getDefaultDriver()]); + if ($request->hasSession()) { + if ($request->session()->has($key = 'password_hash_'.$this->auth->getDefaultDriver())) { + if ($request->session()->get($key) !== $request->user()->getAuthPassword()) { + $this->logout($request); + } + } else { + $this->storePasswordHashInSession($request); + } } return $next($request); } + /** + * Log the user out of the application. + * + * @param \Illuminate\Http\Request $request + * @return void + * + * @throws \Illuminate\Auth\AuthenticationException + */ protected function logout(Request $request) { - $this->auth->logoutCurrentDevice(); + foreach (Arr::wrap(config('sanctum.guard')) as $guard) { + tap($this->auth->guard($guard), function ($guard) { + if ($guard instanceof SessionGuard) { + $guard->logoutCurrentDevice(); + } + }); + } $request->session()->flush(); + + throw new AuthenticationException('Unauthenticated.', [$this->auth->getDefaultDriver()]); + } + + /** + * Store the user's current password hash in the session. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function storePasswordHashInSession($request) + { + if (! $request->user()) { + return; + } + + $request->session()->put([ + 'password_hash_'.$this->auth->getDefaultDriver() => $request->user()->getAuthPassword(), + ]); } } From 66c0a50d31e44e88429f160b2f08cdcabb719bef Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 22 Aug 2023 16:01:57 +0800 Subject: [PATCH 05/28] wip Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotLoggedOutTest.php | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index 87dc072d..7cc9e7b5 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -43,7 +43,7 @@ protected function defineRoutes($router) public function test_middleware_can_authorize_valid_user_using_header() { - $token = PersonalAccessTokenFactory::new()->for( + PersonalAccessTokenFactory::new()->for( $user = UserFactory::new()->create(), 'tokenable') ->create(); @@ -51,26 +51,21 @@ public function test_middleware_can_authorize_valid_user_using_header() 'Authorization' => 'Bearer test', ])->assertOk() ->assertSee($user->name); - } - public function test_middleware_can_authorize_valid_user_using_acting_as() - { - $token = PersonalAccessTokenFactory::new()->for( - $user = UserFactory::new()->create(), 'tokenable') - ->create(); - Sanctum::actingAs($user); + $user->password = bcrypt('laravel'); + $user->save(); - $this->getJson('/sanctum/web/user') - ->assertOk() + $this->getJson('/sanctum/api/user', [ + 'Authorization' => 'Bearer test', + ])->assertOk() ->assertSee($user->name); } + public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change() { - $token = PersonalAccessTokenFactory::new()->for( - $user = UserFactory::new()->create(), 'tokenable') - ->create(); + $user = UserFactory::new()->create(); Sanctum::actingAs($user); From a6b9d9b3221913efa39f478a9e421514c7cd4d9c Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 22 Aug 2023 08:02:06 +0000 Subject: [PATCH 06/28] Apply fixes from StyleCI --- tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index 7cc9e7b5..1b9466b4 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -52,7 +52,6 @@ public function test_middleware_can_authorize_valid_user_using_header() ])->assertOk() ->assertSee($user->name); - $user->password = bcrypt('laravel'); $user->save(); @@ -62,7 +61,6 @@ public function test_middleware_can_authorize_valid_user_using_header() ->assertSee($user->name); } - public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change() { $user = UserFactory::new()->create(); From a9bab0c2f08d68e34bbf0565439c0dc7b0412418 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 22 Aug 2023 21:31:11 +0800 Subject: [PATCH 07/28] wip Signed-off-by: Mior Muhammad Zaki --- .../Middleware/EnsureDeviceHasNotLoggedOutTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index 1b9466b4..471e083f 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -31,13 +31,13 @@ protected function defineRoutes($router) $router->get('/sanctum/api/user', function (Request $request) { abort_if(is_null($request->user()), 401); - return $request->user()->name; + return $request->user()->email; })->middleware('auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); $router->get('/sanctum/web/user', function (Request $request) { abort_if(is_null($request->user()), 401); - return $request->user()->name; + return $request->user()->email; })->middleware('web', 'auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); } @@ -50,7 +50,7 @@ public function test_middleware_can_authorize_valid_user_using_header() $this->getJson('/sanctum/api/user', [ 'Authorization' => 'Bearer test', ])->assertOk() - ->assertSee($user->name); + ->assertSee($user->email); $user->password = bcrypt('laravel'); $user->save(); @@ -58,7 +58,7 @@ public function test_middleware_can_authorize_valid_user_using_header() $this->getJson('/sanctum/api/user', [ 'Authorization' => 'Bearer test', ])->assertOk() - ->assertSee($user->name); + ->assertSee($user->email); } public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change() @@ -69,7 +69,7 @@ public function test_middleware_can_deauthorize_valid_user_using_acting_as_after $this->getJson('/sanctum/web/user') ->assertOk() - ->assertSee($user->name); + ->assertSee($user->email); $user->password = bcrypt('laravel'); $user->save(); From 3b39b7025551652b4611f261d37d3e0d27e42b9e Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 09:50:39 +0800 Subject: [PATCH 08/28] Update src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php Co-authored-by: Patrick O'Meara --- src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index 0e4acb33..31e20282 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -49,7 +49,7 @@ public function handle(Request $request, Closure $next): Response } } - return $next($request); + return tap($next($request), fn () => $this->storePasswordHashInSession($request)); } /** From e85e829f933e77bfcb6a5ed3bd65ca6d34469cf3 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 10:04:47 +0800 Subject: [PATCH 09/28] wip Signed-off-by: Mior Muhammad Zaki --- src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index 31e20282..ffc7a631 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -44,12 +44,12 @@ public function handle(Request $request, Closure $next): Response if ($request->session()->get($key) !== $request->user()->getAuthPassword()) { $this->logout($request); } - } else { - $this->storePasswordHashInSession($request); } + + $this->storePasswordHashInSession($request); } - return tap($next($request), fn () => $this->storePasswordHashInSession($request)); + return $next($request); } /** From 09612258a035e176d1e491719f2357988bf4db90 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 11:45:25 +0800 Subject: [PATCH 10/28] wip Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotBeenLoggedOut.php | 37 ++++++------ .../EnsureDeviceHasNotLoggedOutTest.php | 60 ++++++++++++++++--- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index ffc7a631..066917d5 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Auth\Factory as AuthFactory; use Illuminate\Http\Request; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Symfony\Component\HttpFoundation\Response; class EnsureDeviceHasNotBeenLoggedOut @@ -40,13 +41,20 @@ public function __construct(AuthFactory $auth) public function handle(Request $request, Closure $next): Response { if ($request->hasSession()) { - if ($request->session()->has($key = 'password_hash_'.$this->auth->getDefaultDriver())) { - if ($request->session()->get($key) !== $request->user()->getAuthPassword()) { - $this->logout($request); - } + $guards = Collection::make(Arr::wrap(config('sanctum.guard'))) + ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) + ->filter(fn ($guard) => $guard instanceof SessionGuard); + + $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) + ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); + + if ($shouldLoggedOut->isNotEmpty()) { + $shouldLoggedOut->each(fn ($guard) => $this->logout($request, $guard)); + + throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } - $this->storePasswordHashInSession($request); + $this->storePasswordHashInSession($request, $guards->keys()->first()); } return $next($request); @@ -57,38 +65,29 @@ public function handle(Request $request, Closure $next): Response * * @param \Illuminate\Http\Request $request * @return void - * - * @throws \Illuminate\Auth\AuthenticationException */ - protected function logout(Request $request) + protected function logout(Request $request, SessionGuard $guard) { - foreach (Arr::wrap(config('sanctum.guard')) as $guard) { - tap($this->auth->guard($guard), function ($guard) { - if ($guard instanceof SessionGuard) { - $guard->logoutCurrentDevice(); - } - }); - } + $guard->logoutCurrentDevice(); $request->session()->flush(); - - throw new AuthenticationException('Unauthenticated.', [$this->auth->getDefaultDriver()]); } /** * Store the user's current password hash in the session. * * @param \Illuminate\Http\Request $request + * @param string|null $guard * @return void */ - protected function storePasswordHashInSession($request) + protected function storePasswordHashInSession($request, $guard = null) { if (! $request->user()) { return; } $request->session()->put([ - 'password_hash_'.$this->auth->getDefaultDriver() => $request->user()->getAuthPassword(), + 'password_hash_'.($guard ?? $this->auth->getDefaultDriver()) => $request->user()->getAuthPassword(), ]); } } diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index 471e083f..f8972255 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -4,7 +4,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\Request; -use Laravel\Sanctum\Http\Middleware\EnsureDeviceHasNotBeenLoggedOut; +use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Laravel\Sanctum\Sanctum; use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; @@ -23,6 +23,8 @@ protected function defineEnvironment($app) 'auth.guards.sanctum.provider' => 'users', 'auth.providers.users.model' => User::class, 'database.default' => 'testing', + 'sanctum.middleware.encrypt_cookies' => \Illuminate\Cookie\Middleware\EncryptCookies::class, + 'sanctum.middleware.verify_csrf_token' => \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, ]); } @@ -32,20 +34,26 @@ protected function defineRoutes($router) abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware('auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); + })->middleware([EnsureFrontendRequestsAreStateful::class, 'auth:sanctum']); $router->get('/sanctum/web/user', function (Request $request) { abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware('web', 'auth:sanctum', EnsureDeviceHasNotBeenLoggedOut::class); + })->middleware([EnsureFrontendRequestsAreStateful::class, 'auth:sanctum']); + + $router->get('web/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->email; + })->middleware([EnsureFrontendRequestsAreStateful::class, 'web']); } public function test_middleware_can_authorize_valid_user_using_header() { PersonalAccessTokenFactory::new()->for( $user = UserFactory::new()->create(), 'tokenable') - ->create(); + ->create(); $this->getJson('/sanctum/api/user', [ 'Authorization' => 'Bearer test', @@ -61,20 +69,54 @@ public function test_middleware_can_authorize_valid_user_using_header() ->assertSee($user->email); } - public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change() + public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change_from_sanctum_guard() { $user = UserFactory::new()->create(); - Sanctum::actingAs($user); + Sanctum::actingAs($user, [], 'web'); + + $this->getJson('/web/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); + + $this->getJson('/sanctum/web/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); + + $user->password = bcrypt('laravel'); + $user->save(); + + $this->getJson('/sanctum/web/user', [ + 'origin' => config('app.url'), + ])->assertStatus(401); + } + + public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change_coming_from_web_guard() + { + $user = UserFactory::new()->create(); + + $this->actingAs($user) + ->getJson('/web/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); - $this->getJson('/sanctum/web/user') + $this->getJson('/sanctum/web/user', [ + 'origin' => config('app.url'), + ]) ->assertOk() ->assertSee($user->email); $user->password = bcrypt('laravel'); $user->save(); - $this->getJson('/sanctum/web/user') - ->assertStatus(401); + $this->getJson('/sanctum/web/user', [ + 'origin' => config('app.url'), + ])->assertStatus(401); } } From c3912042c3d3579637269881a9c8d4674138a17e Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 11:54:34 +0800 Subject: [PATCH 11/28] wip Signed-off-by: Mior Muhammad Zaki --- .../Middleware/EnsureDeviceHasNotLoggedOutTest.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index f8972255..c92a4296 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -30,23 +30,26 @@ protected function defineEnvironment($app) protected function defineRoutes($router) { + $webMiddleware = ['web', 'auth.session']; + $apiMiddleware = [EnsureFrontendRequestsAreStateful::class, 'api', 'auth:sanctum']; + $router->get('/sanctum/api/user', function (Request $request) { abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware([EnsureFrontendRequestsAreStateful::class, 'auth:sanctum']); + })->middleware($apiMiddleware); $router->get('/sanctum/web/user', function (Request $request) { abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware([EnsureFrontendRequestsAreStateful::class, 'auth:sanctum']); + })->middleware($apiMiddleware); $router->get('web/user', function (Request $request) { abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware([EnsureFrontendRequestsAreStateful::class, 'web']); + })->middleware($webMiddleware); } public function test_middleware_can_authorize_valid_user_using_header() From 6671f7e56cb229d083d1c84e89dd34a074e43a31 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:07:20 +0800 Subject: [PATCH 12/28] wip Signed-off-by: Mior Muhammad Zaki --- phpunit.xml.dist | 1 + tests/Controller/AuthenticateRequestsTest.php | 87 +++++++++++++++++++ .../EnsureDeviceHasNotLoggedOutTest.php | 33 +++---- 3 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 tests/Controller/AuthenticateRequestsTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8c00924d..5ed70bde 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,6 +14,7 @@ ./tests/Unit + ./tests/Controller ./tests/Feature diff --git a/tests/Controller/AuthenticateRequestsTest.php b/tests/Controller/AuthenticateRequestsTest.php new file mode 100644 index 00000000..74e644ed --- /dev/null +++ b/tests/Controller/AuthenticateRequestsTest.php @@ -0,0 +1,87 @@ +set([ + 'app.key' => 'AckfSECXIvnK5r28GVIWUAxmbBSjTsmF', + 'auth.guards.sanctum.provider' => 'users', + 'auth.providers.users.model' => User::class, + 'database.default' => 'testing', + 'sanctum.middleware.encrypt_cookies' => \Illuminate\Cookie\Middleware\EncryptCookies::class, + 'sanctum.middleware.verify_csrf_token' => \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class, + ]); + } + + protected function defineRoutes($router) + { + $webMiddleware = ['web', 'auth.session']; + $apiMiddleware = [EnsureFrontendRequestsAreStateful::class, 'api', 'auth:sanctum']; + + $router->get('/sanctum/api/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->email; + })->middleware($apiMiddleware); + + $router->get('/sanctum/web/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->email; + })->middleware($apiMiddleware); + + $router->get('web/user', function (Request $request) { + abort_if(is_null($request->user()), 401); + + return $request->user()->email; + })->middleware($webMiddleware); + } + + public function test_can_authorize_valid_user_using_authorization_header() + { + PersonalAccessTokenFactory::new()->for( + $user = UserFactory::new()->create(), 'tokenable' + )->create(); + + $this->getJson('/sanctum/api/user', ['Authorization' => 'Bearer test']) + ->assertOk() + ->assertSee($user->email); + } + + /** + * @dataProvider sanctumGuardsDataProvider + */ + public function test_can_authorize_valid_user_using_sanctum_acting_as($guard) + { + PersonalAccessTokenFactory::new()->for( + $user = UserFactory::new()->create(), 'tokenable' + )->create(); + + Sanctum::actingAs($user, [], $guard); + + $this->getJson('/sanctum/api/user') + ->assertOk() + ->assertSee($user->email); + } + + public static function sanctumGuardsDataProvider() + { + yield [null]; + yield ['web']; + } +} diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index c92a4296..143c5198 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -52,31 +52,14 @@ protected function defineRoutes($router) })->middleware($webMiddleware); } - public function test_middleware_can_authorize_valid_user_using_header() - { - PersonalAccessTokenFactory::new()->for( - $user = UserFactory::new()->create(), 'tokenable') - ->create(); - - $this->getJson('/sanctum/api/user', [ - 'Authorization' => 'Bearer test', - ])->assertOk() - ->assertSee($user->email); - - $user->password = bcrypt('laravel'); - $user->save(); - - $this->getJson('/sanctum/api/user', [ - 'Authorization' => 'Bearer test', - ])->assertOk() - ->assertSee($user->email); - } - - public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change_from_sanctum_guard() + /** + * @dataProvider sanctumGuardsDataProvider + */ + public function test_middleware_can_deauthorize_valid_user_using_acting_as_after_password_change_from_sanctum_guard($guard) { $user = UserFactory::new()->create(); - Sanctum::actingAs($user, [], 'web'); + Sanctum::actingAs($user, [], $guard); $this->getJson('/web/user', [ 'origin' => config('app.url'), @@ -122,4 +105,10 @@ public function test_middleware_can_deauthorize_valid_user_using_acting_as_after 'origin' => config('app.url'), ])->assertStatus(401); } + + public static function sanctumGuardsDataProvider() + { + yield [null]; + yield ['web']; + } } From 167f77b897d79a9d43a2d0596f93b22306a4ea6f Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 23 Aug 2023 04:07:40 +0000 Subject: [PATCH 13/28] Apply fixes from StyleCI --- tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php index 143c5198..f969e95d 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php @@ -9,7 +9,6 @@ use Orchestra\Testbench\Concerns\WithWorkbench; use Orchestra\Testbench\TestCase; use Workbench\App\Models\User; -use Workbench\Database\Factories\PersonalAccessTokenFactory; use Workbench\Database\Factories\UserFactory; class EnsureDeviceHasNotLoggedOutTest extends TestCase From b95b8aa274a904564923c0fa133574e7a84a3564 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:12:11 +0800 Subject: [PATCH 14/28] wip Signed-off-by: Mior Muhammad Zaki --- src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index 066917d5..21a34d40 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -54,7 +54,7 @@ public function handle(Request $request, Closure $next): Response throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } - $this->storePasswordHashInSession($request, $guards->keys()->first()); + $this->storePasswordHashInSession($request, $guards->values()->first()); } return $next($request); @@ -64,6 +64,7 @@ public function handle(Request $request, Closure $next): Response * Log the user out of the application. * * @param \Illuminate\Http\Request $request + * @param \Illuminate\Auth\SessionGuard $guard * @return void */ protected function logout(Request $request, SessionGuard $guard) @@ -77,17 +78,17 @@ protected function logout(Request $request, SessionGuard $guard) * Store the user's current password hash in the session. * * @param \Illuminate\Http\Request $request - * @param string|null $guard + * @param \Illuminate\Auth\SessionGuard $guard * @return void */ - protected function storePasswordHashInSession($request, $guard = null) + protected function storePasswordHashInSession($request, SessionGuard $guard) { if (! $request->user()) { return; } $request->session()->put([ - 'password_hash_'.($guard ?? $this->auth->getDefaultDriver()) => $request->user()->getAuthPassword(), + "password_hash_{$guard->name}" => $request->user()->getAuthPassword(), ]); } } From d084dd4c7d816f8336e29087fbe256f51d9a5095 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:15:34 +0800 Subject: [PATCH 15/28] wip Signed-off-by: Mior Muhammad Zaki --- tests/Controller/AuthenticateRequestsTest.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/Controller/AuthenticateRequestsTest.php b/tests/Controller/AuthenticateRequestsTest.php index 74e644ed..9a616b74 100644 --- a/tests/Controller/AuthenticateRequestsTest.php +++ b/tests/Controller/AuthenticateRequestsTest.php @@ -30,7 +30,6 @@ protected function defineEnvironment($app) protected function defineRoutes($router) { - $webMiddleware = ['web', 'auth.session']; $apiMiddleware = [EnsureFrontendRequestsAreStateful::class, 'api', 'auth:sanctum']; $router->get('/sanctum/api/user', function (Request $request) { @@ -44,12 +43,6 @@ protected function defineRoutes($router) return $request->user()->email; })->middleware($apiMiddleware); - - $router->get('web/user', function (Request $request) { - abort_if(is_null($request->user()), 401); - - return $request->user()->email; - })->middleware($webMiddleware); } public function test_can_authorize_valid_user_using_authorization_header() From ab1a0ee6572f87ac0cd834ff40d7863d9b680909 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:18:24 +0800 Subject: [PATCH 16/28] wip Signed-off-by: Mior Muhammad Zaki --- src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index 21a34d40..49e7143b 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -54,7 +54,7 @@ public function handle(Request $request, Closure $next): Response throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } - $this->storePasswordHashInSession($request, $guards->values()->first()); + $this->storePasswordHashInSession($request, $guards->keys()->first()); } return $next($request); @@ -78,17 +78,17 @@ protected function logout(Request $request, SessionGuard $guard) * Store the user's current password hash in the session. * * @param \Illuminate\Http\Request $request - * @param \Illuminate\Auth\SessionGuard $guard + * @param string $guard * @return void */ - protected function storePasswordHashInSession($request, SessionGuard $guard) + protected function storePasswordHashInSession($request, string $guard) { if (! $request->user()) { return; } $request->session()->put([ - "password_hash_{$guard->name}" => $request->user()->getAuthPassword(), + "password_hash_{$guard}" => $request->user()->getAuthPassword(), ]); } } From 0dd3fdd3f30bb0bcdba2c773dea89d16d3dab46b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:28:55 +0800 Subject: [PATCH 17/28] wip Signed-off-by: Mior Muhammad Zaki --- .../EnsureDeviceHasNotBeenLoggedOut.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php index 49e7143b..b5511116 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php @@ -49,7 +49,9 @@ public function handle(Request $request, Closure $next): Response ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); if ($shouldLoggedOut->isNotEmpty()) { - $shouldLoggedOut->each(fn ($guard) => $this->logout($request, $guard)); + $shouldLoggedOut->each->logoutCurrentDevice(); + + $request->session()->flush(); throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } @@ -60,20 +62,6 @@ public function handle(Request $request, Closure $next): Response return $next($request); } - /** - * Log the user out of the application. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Auth\SessionGuard $guard - * @return void - */ - protected function logout(Request $request, SessionGuard $guard) - { - $guard->logoutCurrentDevice(); - - $request->session()->flush(); - } - /** * Store the user's current password hash in the session. * From cd30e349efeb8f1e5ff5a2264780e63af6df3040 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 12:44:14 +0800 Subject: [PATCH 18/28] wip Signed-off-by: Mior Muhammad Zaki --- ...eDeviceHasNotBeenLoggedOut.php => AuthenticateSession.php} | 2 +- src/Http/Middleware/EnsureFrontendRequestsAreStateful.php | 2 +- .../FrontendRequestsAreStatefulTest.php} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/Http/Middleware/{EnsureDeviceHasNotBeenLoggedOut.php => AuthenticateSession.php} (98%) rename tests/{Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php => Controller/FrontendRequestsAreStatefulTest.php} (96%) diff --git a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php b/src/Http/Middleware/AuthenticateSession.php similarity index 98% rename from src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php rename to src/Http/Middleware/AuthenticateSession.php index b5511116..fd2c73a8 100644 --- a/src/Http/Middleware/EnsureDeviceHasNotBeenLoggedOut.php +++ b/src/Http/Middleware/AuthenticateSession.php @@ -11,7 +11,7 @@ use Illuminate\Support\Collection; use Symfony\Component\HttpFoundation\Response; -class EnsureDeviceHasNotBeenLoggedOut +class AuthenticateSession { /** * The authentication factory implementation. diff --git a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php index 2291bd1d..e0e5f577 100644 --- a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php +++ b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php @@ -52,7 +52,7 @@ protected function frontendMiddleware() \Illuminate\Session\Middleware\StartSession::class, config('sanctum.middleware.validate_csrf_token'), config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class), - config('sanctum.middleware.ensure_not_logged_out', EnsureDeviceHasNotBeenLoggedOut::class), + config('sanctum.middleware.auth_session') ?? AuthenticateSession::class, ]))); array_unshift($middleware, function ($request, $next) { diff --git a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php similarity index 96% rename from tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php rename to tests/Controller/FrontendRequestsAreStatefulTest.php index f969e95d..33aac600 100644 --- a/tests/Feature/Middleware/EnsureDeviceHasNotLoggedOutTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -1,6 +1,6 @@ Date: Wed, 23 Aug 2023 13:28:33 +0800 Subject: [PATCH 19/28] Apply suggestions from code review Co-authored-by: Patrick O'Meara --- .../FrontendRequestsAreStatefulTest.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/Controller/FrontendRequestsAreStatefulTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php index 33aac600..8658cfb0 100644 --- a/tests/Controller/FrontendRequestsAreStatefulTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -49,6 +49,44 @@ protected function defineRoutes($router) return $request->user()->email; })->middleware($webMiddleware); + + $router->post('/sanctum/api/password', function (Request $request) { + abort_if(is_null($request->user()), 401); + + $request->user()->update(['password' => bcrypt('laravel')]); + + return $request->user()->email; + })->middleware($apiMiddleware); + } + + public function test_middleware_keeps_session_logged_in_when_sanctum_request_changes_password() + { + $user = UserFactory::new()->create(); + + $this->actingAs($user) + ->getJson('/web/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); + + $this->getJson('/sanctum/api/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); + + $this->postJson('/sanctum/api/password', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); + + $this->getJson('/sanctum/api/user', [ + 'origin' => config('app.url'), + ]) + ->assertOk() + ->assertSee($user->email); } /** From 45815c26b9ca63018980983c3807f572f7d2615e Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 23 Aug 2023 05:28:41 +0000 Subject: [PATCH 20/28] Apply fixes from StyleCI --- tests/Controller/FrontendRequestsAreStatefulTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Controller/FrontendRequestsAreStatefulTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php index 8658cfb0..5603489a 100644 --- a/tests/Controller/FrontendRequestsAreStatefulTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -58,7 +58,7 @@ protected function defineRoutes($router) return $request->user()->email; })->middleware($apiMiddleware); } - + public function test_middleware_keeps_session_logged_in_when_sanctum_request_changes_password() { $user = UserFactory::new()->create(); From 809dda728e35a31ac3e08a94febff720728f97ef Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 13:30:03 +0800 Subject: [PATCH 21/28] wip Signed-off-by: Mior Muhammad Zaki --- .../Controller/FrontendRequestsAreStatefulTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Controller/FrontendRequestsAreStatefulTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php index 5603489a..cbf714a7 100644 --- a/tests/Controller/FrontendRequestsAreStatefulTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -38,25 +38,25 @@ protected function defineRoutes($router) return $request->user()->email; })->middleware($apiMiddleware); - $router->get('/sanctum/web/user', function (Request $request) { + $router->post('/sanctum/api/password', function (Request $request) { abort_if(is_null($request->user()), 401); + $request->user()->update(['password' => bcrypt('laravel')]); + return $request->user()->email; })->middleware($apiMiddleware); - $router->get('web/user', function (Request $request) { + $router->get('/sanctum/web/user', function (Request $request) { abort_if(is_null($request->user()), 401); return $request->user()->email; - })->middleware($webMiddleware); + })->middleware($apiMiddleware); - $router->post('/sanctum/api/password', function (Request $request) { + $router->get('web/user', function (Request $request) { abort_if(is_null($request->user()), 401); - $request->user()->update(['password' => bcrypt('laravel')]); - return $request->user()->email; - })->middleware($apiMiddleware); + })->middleware($webMiddleware); } public function test_middleware_keeps_session_logged_in_when_sanctum_request_changes_password() From 67ce97058684a374d83e576add052783b776bcdd Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 13:53:27 +0800 Subject: [PATCH 22/28] wip Signed-off-by: Mior Muhammad Zaki --- src/Http/Middleware/AuthenticateSession.php | 20 ++++++++++++------- .../FrontendRequestsAreStatefulTest.php | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Http/Middleware/AuthenticateSession.php b/src/Http/Middleware/AuthenticateSession.php index fd2c73a8..f2e2b77a 100644 --- a/src/Http/Middleware/AuthenticateSession.php +++ b/src/Http/Middleware/AuthenticateSession.php @@ -40,11 +40,15 @@ public function __construct(AuthFactory $auth) */ public function handle(Request $request, Closure $next): Response { - if ($request->hasSession()) { - $guards = Collection::make(Arr::wrap(config('sanctum.guard'))) - ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) - ->filter(fn ($guard) => $guard instanceof SessionGuard); + if (! $request->hasSession() || ! $request->user()) { + return $next($request); + } + + $guards = Collection::make(Arr::wrap(config('sanctum.guard'))) + ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) + ->filter(fn ($guard) => $guard instanceof SessionGuard); + if ($request->hasSession()) { $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); @@ -55,11 +59,13 @@ public function handle(Request $request, Closure $next): Response throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } - - $this->storePasswordHashInSession($request, $guards->keys()->first()); } - return $next($request); + return tap($next($request), function () use ($request, $guards) { + if (! is_null($request->user())) { + $this->storePasswordHashInSession($request, $guards->keys()->first()); + } + }); } /** diff --git a/tests/Controller/FrontendRequestsAreStatefulTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php index cbf714a7..c61e1276 100644 --- a/tests/Controller/FrontendRequestsAreStatefulTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -70,13 +70,13 @@ public function test_middleware_keeps_session_logged_in_when_sanctum_request_cha ->assertOk() ->assertSee($user->email); - $this->getJson('/sanctum/api/user', [ + $this->actingAs($user)->getJson('/sanctum/api/user', [ 'origin' => config('app.url'), ]) ->assertOk() ->assertSee($user->email); - $this->postJson('/sanctum/api/password', [ + $this->postJson('/sanctum/api/password', [], [ 'origin' => config('app.url'), ]) ->assertOk() From b123047fcd6db35644a464ed8a92856601b66b3c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 13:55:52 +0800 Subject: [PATCH 23/28] wip Signed-off-by: Mior Muhammad Zaki --- tests/Controller/FrontendRequestsAreStatefulTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Controller/FrontendRequestsAreStatefulTest.php b/tests/Controller/FrontendRequestsAreStatefulTest.php index c61e1276..424bff7b 100644 --- a/tests/Controller/FrontendRequestsAreStatefulTest.php +++ b/tests/Controller/FrontendRequestsAreStatefulTest.php @@ -70,7 +70,7 @@ public function test_middleware_keeps_session_logged_in_when_sanctum_request_cha ->assertOk() ->assertSee($user->email); - $this->actingAs($user)->getJson('/sanctum/api/user', [ + $this->getJson('/sanctum/api/user', [ 'origin' => config('app.url'), ]) ->assertOk() From 1e55335bf32728f3b2729810380a7886d0fd6604 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 18:37:54 +0800 Subject: [PATCH 24/28] wip Signed-off-by: Mior Muhammad Zaki --- config/sanctum.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/sanctum.php b/config/sanctum.php index 4c01d7fe..c076a233 100644 --- a/config/sanctum.php +++ b/config/sanctum.php @@ -1,5 +1,6 @@ [ 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, + 'auth_session' => AuthenticateSession::class, ], ]; From 91e287e75529ed6efd23ccd891551485035fde8b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 19:36:10 +0800 Subject: [PATCH 25/28] Update src/Http/Middleware/AuthenticateSession.php --- src/Http/Middleware/AuthenticateSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Middleware/AuthenticateSession.php b/src/Http/Middleware/AuthenticateSession.php index f2e2b77a..bf0aefc7 100644 --- a/src/Http/Middleware/AuthenticateSession.php +++ b/src/Http/Middleware/AuthenticateSession.php @@ -48,7 +48,7 @@ public function handle(Request $request, Closure $next): Response ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) ->filter(fn ($guard) => $guard instanceof SessionGuard); - if ($request->hasSession()) { + if ($request->user()) { $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); From b21171b706fb3cae4636e8736e1046d4a5add8ea Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 23 Aug 2023 19:44:10 +0800 Subject: [PATCH 26/28] wip Signed-off-by: Mior Muhammad Zaki --- src/Http/Middleware/AuthenticateSession.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Http/Middleware/AuthenticateSession.php b/src/Http/Middleware/AuthenticateSession.php index bf0aefc7..8726c391 100644 --- a/src/Http/Middleware/AuthenticateSession.php +++ b/src/Http/Middleware/AuthenticateSession.php @@ -40,7 +40,7 @@ public function __construct(AuthFactory $auth) */ public function handle(Request $request, Closure $next): Response { - if (! $request->hasSession() || ! $request->user()) { + if (! ($request->hasSession() && $request->user())) { return $next($request); } @@ -48,17 +48,15 @@ public function handle(Request $request, Closure $next): Response ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) ->filter(fn ($guard) => $guard instanceof SessionGuard); - if ($request->user()) { - $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) - ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); + $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) + ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); - if ($shouldLoggedOut->isNotEmpty()) { - $shouldLoggedOut->each->logoutCurrentDevice(); + if ($shouldLoggedOut->isNotEmpty()) { + $shouldLoggedOut->each->logoutCurrentDevice(); - $request->session()->flush(); + $request->session()->flush(); - throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); - } + throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); } return tap($next($request), function () use ($request, $guards) { From f52ef2a497b9f4c90db80f8a3df78996df6888ff Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 30 Aug 2023 16:18:06 -0500 Subject: [PATCH 27/28] formatting --- src/Http/Middleware/AuthenticateSession.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Http/Middleware/AuthenticateSession.php b/src/Http/Middleware/AuthenticateSession.php index 8726c391..965dae40 100644 --- a/src/Http/Middleware/AuthenticateSession.php +++ b/src/Http/Middleware/AuthenticateSession.php @@ -40,7 +40,7 @@ public function __construct(AuthFactory $auth) */ public function handle(Request $request, Closure $next): Response { - if (! ($request->hasSession() && $request->user())) { + if (! $request->hasSession() || ! $request->user()) { return $next($request); } @@ -48,15 +48,19 @@ public function handle(Request $request, Closure $next): Response ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) ->filter(fn ($guard) => $guard instanceof SessionGuard); - $shouldLoggedOut = $guards->filter(fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver)) - ->filter(fn ($quard, $driver) => $request->session()->get('password_hash_'.$driver) !== $request->user()->getAuthPassword()); + $shouldLogout = $guards->filter( + fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver) + )->filter( + fn ($guard, $driver) => $request->session()->get('password_hash_'.$driver) !== + $request->user()->getAuthPassword() + ); - if ($shouldLoggedOut->isNotEmpty()) { - $shouldLoggedOut->each->logoutCurrentDevice(); + if ($shouldLogout->isNotEmpty()) { + $shouldLogout->each->logoutCurrentDevice(); $request->session()->flush(); - throw new AuthenticationException('Unauthenticated.', [...$shouldLoggedOut->keys()->all(), 'sanctum']); + throw new AuthenticationException('Unauthenticated.', [...$shouldLogout->keys()->all(), 'sanctum']); } return tap($next($request), function () use ($request, $guards) { From 461d631763a29d74d727ac875774bcee96448a0a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 30 Aug 2023 16:20:45 -0500 Subject: [PATCH 28/28] formatting --- config/sanctum.php | 3 +-- src/Http/Middleware/EnsureFrontendRequestsAreStateful.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config/sanctum.php b/config/sanctum.php index c076a233..3edde812 100644 --- a/config/sanctum.php +++ b/config/sanctum.php @@ -1,6 +1,5 @@ [ 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, - 'auth_session' => AuthenticateSession::class, + 'authenticate_session' => \Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, ], ]; diff --git a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php index e0e5f577..78dffc69 100644 --- a/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php +++ b/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php @@ -52,7 +52,7 @@ protected function frontendMiddleware() \Illuminate\Session\Middleware\StartSession::class, config('sanctum.middleware.validate_csrf_token'), config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class), - config('sanctum.middleware.auth_session') ?? AuthenticateSession::class, + config('sanctum.middleware.authenticate_session'), ]))); array_unshift($middleware, function ($request, $next) {