From a5d172ac3f57170f08d56f9d49bca4883c05e05e Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 7 Dec 2023 10:32:28 -0500 Subject: [PATCH] feat(SDK-4732): Implement support for Back-Channel Logout (#435) --- composer.json | 4 +-- config/auth0.php | 2 ++ docs/BackchannelLogout.md | 17 +++++++++++++ docs/Configuration.md | 2 ++ src/Configuration.php | 11 +++++++++ src/Entities/InstanceEntityAbstract.php | 33 +++++++++++++++++++++++++ tests/Unit/ServiceTest.php | 23 +++++++++++++++++ 7 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 docs/BackchannelLogout.md diff --git a/composer.json b/composer.json index 357616b6..98a4f4d1 100644 --- a/composer.json +++ b/composer.json @@ -35,9 +35,9 @@ "source": "https://github.com/auth0/laravel-auth0" }, "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", - "auth0/auth0-php": "^8.7", + "auth0/auth0-php": "^8.10", "illuminate/contracts": "^9 || ^10", "illuminate/http": "^9 || ^10", "illuminate/support": "^9 || ^10", diff --git a/config/auth0.php b/config/auth0.php index 7a5664f6..f403e40a 100644 --- a/config/auth0.php +++ b/config/auth0.php @@ -37,6 +37,8 @@ Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_KEY => Configuration::get(Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_KEY), Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_ALGORITHM => Configuration::get(Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_ALGORITHM), Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST => Configuration::get(Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST), + Configuration::CONFIG_BACKCHANNEL_LOGOUT_CACHE => Configuration::get(Configuration::CONFIG_BACKCHANNEL_LOGOUT_CACHE), + Configuration::CONFIG_BACKCHANNEL_LOGOUT_EXPIRES => Configuration::get(Configuration::CONFIG_BACKCHANNEL_LOGOUT_EXPIRES), ], 'api' => [ diff --git a/docs/BackchannelLogout.md b/docs/BackchannelLogout.md new file mode 100644 index 00000000..6e109cdf --- /dev/null +++ b/docs/BackchannelLogout.md @@ -0,0 +1,17 @@ +# Backchannel Logout + +The Auth0 Laravel SDK supports [Backchannel Logout](https://auth0.com/docs/authenticate/login/logout/back-channel-logout) from v7.12 onward. To use this feature, some additional configuration is necessary: + +1. **Add a new route to your application.** This route must be publicly accessible. Auth0 will use it to send backchannel logout requests to your application. For example: + +```php +Route::post('/backchannel', function (Request $request) { + if ($request->has('logout_token')) { + app('auth0')->handleBackchannelLogout($request->string('logout_token', '')->trim()); + } +}); +``` + +2. **Configure your Auth0 tenant to use Backchannel Logout.** See the [Auth0 documentation](https://auth0.com/docs/authenticate/login/logout/back-channel-logout/configure-back-channel-logout) for more information on how to do this. Please ensure you point the Logout URI to the backchannel route we just added to your application. + +Note: If your application's configuration assigns `false` to the `backchannelLogoutCache` SDK configuration property, this feature will be disabled entirely. diff --git a/docs/Configuration.md b/docs/Configuration.md index f8d7c799..8f9b5876 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -85,6 +85,8 @@ The following environment variables are supported, but should not be adjusted un | `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` | `String` The key to use for signing client assertions. | | `AUTH0_CLIENT_ASSERTION_SIGNING_ALGORITHM` | `String` The algorithm to use for signing client assertions. Defaults to `RS256`. | | `AUTH0_PUSHED_AUTHORIZATION_REQUEST` | `Boolean` Whether the SDK should use Pushed Authorization Requests during authentication. Note that your tenant must have this feature enabled. Defaults to `false`. | +| `AUTH0_BACKCHANNEL_LOGOUT_CACHE` | `String (class name)` A PSR-6 class to use for caching backchannel logout tokens. | +| `AUTH0_BACKCHANNEL_LOGOUT_EXPIRES` | `Integer` How long (in seconds) to cache a backchannel logout token. Defaults to `2592000` (30 days). | ### Order of Priority diff --git a/src/Configuration.php b/src/Configuration.php index c7a27ece..479bf2d5 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -50,6 +50,7 @@ final class Configuration implements ConfigurationContract self::CONFIG_TOKEN_CACHE_TTL, self::CONFIG_HTTP_MAX_RETRIES, self::CONFIG_COOKIE_EXPIRES, + self::CONFIG_BACKCHANNEL_LOGOUT_EXPIRES, ]; /** @@ -57,6 +58,16 @@ final class Configuration implements ConfigurationContract */ public const CONFIG_AUDIENCE = 'audience'; + /** + * @var string + */ + public const CONFIG_BACKCHANNEL_LOGOUT_CACHE = 'backchannelLogoutCache'; + + /** + * @var string + */ + public const CONFIG_BACKCHANNEL_LOGOUT_EXPIRES = 'backchannelLogoutExpires'; + /** * @var string */ diff --git a/src/Entities/InstanceEntityAbstract.php b/src/Entities/InstanceEntityAbstract.php index e1f54f2c..36d2929f 100644 --- a/src/Entities/InstanceEntityAbstract.php +++ b/src/Entities/InstanceEntityAbstract.php @@ -29,6 +29,7 @@ public function __construct( protected ?CacheItemPoolInterface $tokenCachePool = null, protected ?CacheItemPoolInterface $managementTokenCachePool = null, protected ?string $guardConfigurationKey = null, + protected ?CacheItemPoolInterface $backchannelLogoutCachePool = null, ) { } @@ -119,6 +120,29 @@ abstract public function setConfiguration( SdkConfiguration | array | null $configuration = null, ): self; + protected function bootBackchannelLogoutCache(array $config): array + { + $backchannelLogoutCache = $config['backchannelLogoutCache'] ?? null; + + if (false === $backchannelLogoutCache) { + unset($config['backchannelLogoutCache']); + + return $config; + } + + if (null === $backchannelLogoutCache) { + $backchannelLogoutCache = $this->getBackchannelLogoutCachePool(); + } + + if (is_string($backchannelLogoutCache)) { + $backchannelLogoutCache = app(trim($backchannelLogoutCache)); + } + + $config['backchannelLogoutCache'] = $backchannelLogoutCache instanceof CacheItemPoolInterface ? $backchannelLogoutCache : null; + + return $config; + } + protected function bootManagementTokenCache(array $config): array { $managementTokenCache = $config['managementTokenCache'] ?? null; @@ -256,6 +280,15 @@ protected function createConfiguration( return $sdkConfiguration; } + protected function getBackchannelLogoutCachePool(): CacheItemPoolInterface + { + if (! $this->backchannelLogoutCachePool instanceof CacheItemPoolInterface) { + $this->backchannelLogoutCachePool = app(CacheBridge::class); + } + + return $this->backchannelLogoutCachePool; + } + protected function getManagementTokenCachePool(): CacheItemPoolInterface { if (! $this->managementTokenCachePool instanceof CacheItemPoolInterface) { diff --git a/tests/Unit/ServiceTest.php b/tests/Unit/ServiceTest.php index 9a5d0b1e..6b5e677e 100644 --- a/tests/Unit/ServiceTest.php +++ b/tests/Unit/ServiceTest.php @@ -188,6 +188,29 @@ ->tokenCache->toBeInstanceOf(CacheItemPoolInterface::class); }); +test('bootBackchannelLogoutCache() behaves as expected', function (): void { + $method = new ReflectionMethod(Service::class, 'bootBackchannelLogoutCache'); + $method->setAccessible(true); + + expect($method->invoke($this->laravel, [])) + ->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class); + + expect($method->invoke($this->laravel, ['backchannelLogoutCache' => null])) + ->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class); + + expect($method->invoke($this->laravel, ['backchannelLogoutCache' => CacheBridge::class])) + ->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class); + + expect($method->invoke($this->laravel, ['backchannelLogoutCache' => false])) + ->backchannelLogoutCache->toBeNull(); + + expect($method->invoke($this->laravel, ['backchannelLogoutCache' => MemoryStore::class])) + ->backchannelLogoutCache->toBeNull(); + + expect($method->invoke($this->laravel, ['backchannelLogoutCache' => 'cache.psr6'])) + ->backchannelLogoutCache->toBeInstanceOf(CacheItemPoolInterface::class); +}); + // test('bootManagementTokenCache() behaves as expected', function (): void { // $method = new ReflectionMethod(Service::class, 'bootManagementTokenCache'); // $method->setAccessible(true);