Skip to content

Commit

Permalink
feat: Support for Back-Channel Logout (#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
evansims committed Dec 12, 2023
1 parent f7352e2 commit 35d05a6
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 18 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ WordPress Plugin for [Auth0](https://auth0.com) Authentication

:rocket: [Getting Started](#getting-started) - :computer: [SDK Usage](#sdk-usage) - 📆 [Support Policy](#support-policy) - :speech_balloon: [Feedback](#feedback)

## Plugin Overview
## Overview

The Auth0 WordPress plugin replaces the standard WordPress login flow with a new authentication process using Auth0's Universal Login experience. This enables you to secure your WordPress site with Auth0's advanced features, such as MFA, SSO, Passwordless, PassKey, and so on.

> [!IMPORTANT]
> This plugin is **NOT** a SDK (Software Development Kit.) We do not provide support for customizing the plugin's behavior or integrating it into WordPress in any way beyond what is expressly explained here. If you are looking for an SDK, please build a custom solution from the [Auth0-PHP SDK](https://github.com/auth0/auth0-php) instead.
> This plugin is **NOT** a SDK (Software Development Kit.) It's APIs are internal and not intended for developers to extend directly. We do not support altering the plugin's behavior or integrating it in any way beyond what is outlined in this README. If you're looking to build a more extensive integration, please create a solution using the [Auth0-PHP SDK](https://github.com/auth0/auth0-php) instead.
> [!WARNING]
> v4 of the plugin is no longer supported as of June 2023. We are no longer providing new features or bugfixes for that release. Please upgrade to v5 as soon as possible.
## Getting Started

Expand All @@ -25,9 +28,6 @@ The Auth0 WordPress plugin replaces the standard WordPress login flow with a new
### Installation

> [!WARNING]
> v4 of the plugin is no longer supported as of June 2023. We are no longer providing new features or bugfixes for that release. Please upgrade to v5 as soon as possible.
<!-- // Disabled while we complete this distribution configuration
#### Release Package
Releases are available from the GitHub repository [github.com/auth0/wordpress/releases](https://github.com/auth0/wordpress/releases), packaged as ZIP archives. Every release has an accompanying signature file for verification if desired.
Expand All @@ -54,9 +54,11 @@ openssl dgst -verify signing.key.pub -keyform PEM -sha256 -signature RELEASE.zip

#### Composer

The plugin supports installation through [Composer](https://getcomposer.org/), and is [WPackagist](https://wpackagist.org/) compatible. This approach is preferred when using [Bedrock](https://roots.io/bedrock/) or [WordPress Core](https://github.com/johnpbloch/wordpress-core-installer), but will work with virtually any WordPress installation.
The plugin supports installation through [Composer](https://getcomposer.org/), and is [WPackagist](https://wpackagist.org/) compatible. This approach is preferred when using [Bedrock](https://roots.io/bedrock/), but will work with virtually any WordPress installation.

When using Composer-based WordPress configurations like Bedrock, you'll usually run this command from the root WordPress installation directory. Still, it's advisable to check the documentation the project's maintainers provided for the best guidance. This command can be run from the `wp-content/plugins` sub-directory for standard WordPress installations.
For [Bedrock](https://roots.io/bedrock/) installations, you'll usually run this command from the root WordPress installation directory, but check the documentation the project's maintainers provide for the best guidance.

For standard WordPress installations, this command can be run from the `wp-content/plugins` sub-directory.

```
composer require symfony/http-client nyholm/psr7 auth0/wordpress:^5.0
Expand All @@ -76,7 +78,10 @@ If you are using Bedrock or another Composer-based configuration, you can try in
<!-- // Disabled while we complete this distribution configuration
#### WordPress Dashboard
Installation from your WordPress dashboard is also supported. This approach first installs a small setup script that will verify that your host environment is compatible. Afterward, the latest plugin release will be downloaded from the GitHub repository, have its file signature verified, and ultimately installed.
> [!CAUTION]
> We recommend against using the WordPress Dashboard or Marketplace to install or update the plugin. Automattic does not implement reliable security measures to protect plugins from tampering, and this approach presents a supply chain risk. It is not recommended for production sites.
Installation from your WordPress dashboard is supported. This approach first installs a small setup script that will verify that your host environment is compatible. Afterward, the latest plugin release will be downloaded from the GitHub repository, have its file signature verified, and ultimately installed.
- Open your WordPress Dashboard.
- Click 'Plugins", then 'Add New,' and search for 'Auth0'.
Expand Down
29 changes: 29 additions & 0 deletions src/Actions/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Auth0\SDK\Exception\StateException;
use Auth0\SDK\Store\CookieStore;
use Auth0\WordPress\Database;
use Auth0\WordPress\Utilities\Sanitize;
use Throwable;
use WP_Error;
use WP_User;
Expand Down Expand Up @@ -415,6 +416,34 @@ public function onLogin(): void
return;
}

if (isset($_GET['auth0_fb'])) {
$incomingFallbackRequest = Sanitize::string($_GET['auth0_fb']);
$fallbackSecret = $this->getPlugin()->getOptionString('authentication', 'fallback_secret');

if ($incomingFallbackRequest === $fallbackSecret) {
return;
}

// Ignore invalid requests; continue as normal.
}

if (isset($_GET['auth0_bcl']) && isset($_POST['logout_token'])) {
$incomingBackchannelLogoutRequest = Sanitize::string($_GET['auth0_bcl']);
$backchannelLogoutSecret = $this->getPlugin()->getOptionString('authentication', 'backchannel_logout_secret');

if ($incomingBackchannelLogoutRequest === $backchannelLogoutSecret) {
$logoutToken = Sanitize::string($_POST['logout_token']);

try {
$this->getSdk()->handleBackchannelLogout($logoutToken);
exit();
} catch (Throwable) {
}
}

// Ignore invalid requests; continue as normal.
}

// Don't allow caching of this route
nocache_headers();

Expand Down
171 changes: 164 additions & 7 deletions src/Actions/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class Configuration extends Base
'description' => '',
'options' => [
'enable' => [
'title' => 'Manage Authentication',
'title' => 'Enable Authentication',
'type' => 'boolean',
'enabled' => 'isPluginReady',
'description' => ['getOptionDescription', 'enable'],
Expand All @@ -39,7 +39,7 @@ final class Configuration extends Base
],
],
'accounts' => [
'title' => 'WordPress Account Management',
'title' => 'WordPress Users Management',
'description' => '',
'options' => [
'matching' => [
Expand All @@ -48,15 +48,15 @@ final class Configuration extends Base
'enabled' => 'isPluginReady',
'description' => '<b>Flexible</b> allows users to sign in using more than one connection type.<br /><b>Strict</b> is more secure, but may lead to confusion for users who forget their sign in method.',
'select' => [
'flexible' => 'Flexible: Match Verified Email Addresses to Accounts',
'strict' => 'Strict: Match Unique Connections to Accounts',
'flexible' => 'Flexible: Match Verified Email Addresses to Users',
'strict' => 'Strict: Match Unique Connections to Users',
],
],
'missing' => [
'title' => 'Absentee Accounts',
'title' => 'Missing Users',
'type' => 'text',
'enabled' => 'isPluginReady',
'description' => 'What to do after a successful sign in, but there is no matching WordPress account.<br />For Database Connections, the "Disable Sign Ups" setting will be honored prior to this.',
'description' => 'What to do after a successful sign in, but there is no matching WordPress account.<br />For Database Connections, the "Disable Sign Ups" setting takes priority over this option.',
'select' => [
'reject' => 'Deny access',
'create' => 'Create account',
Expand All @@ -66,7 +66,7 @@ final class Configuration extends Base
'title' => 'Default Role',
'type' => 'text',
'enabled' => 'isPluginReady',
'description' => 'The role to assign new WordPress accounts created by the plugin.',
'description' => 'The role to assign new WordPress users created by the plugin.',
'select' => 'getRoleOptions',
],
'passwordless' => [
Expand Down Expand Up @@ -219,6 +219,12 @@ final class Configuration extends Base
'false' => 'Disabled',
],
],
'fallback_secret' => [
'title' => 'Fallback Secret',
'type' => 'password',
'enabled' => 'isPluginReady',
'description' => ['getOptionDescription', 'fallback_secret'],
],
],
],
'client_advanced' => [
Expand Down Expand Up @@ -382,10 +388,60 @@ final class Configuration extends Base
],
],
],
'backchannel_logout' => [
'title' => 'Back-Channel Logout',
'description' => 'You must configure your <a href="https://auth0.com/docs/authenticate/login/logout/back-channel-logout/configure-back-channel-logout" target="_blank">Auth0 tenant</a> to enable this feature.',
'options' => [
'enabled' => [
'title' => 'Enabled',
'type' => 'boolean',
'enabled' => 'isPluginReady',
'description' => 'Enable this if your site is <b>exclusively</b> served over HTTPS.',
'select' => [
'false' => 'Disabled',
'true' => 'Enabled',
],
],
'ttl' => [
'title' => 'Logout Expiration',
'type' => 'int',
'enabled' => 'isPluginReady',
'description' => 'How long before unclaimed Back-Channel Logout tokens expire.',
'select' => [
0 => 'Default (1 month)',
1800 => '30 minutes',
3600 => '1 hour',
3600 * 6 => '6 hours',
3600 * 12 => '12 hours',
3600 * 24 => '1 day',
86400 * 2 => '2 days',
86400 * 4 => '4 days',
86400 * 7 => '1 week',
86400 * 14 => '2 weeks',
86400 * 30 => '1 month',
],
],
'secret' => [
'title' => 'Secret',
'type' => 'password',
'enabled' => 'isPluginReady',
'description' => ['getOptionDescription', 'backchannel_logout_secret'],
],
],
],
],
],
self::CONST_PAGE_TOOLS => [
'title' => 'Auth0 — Tools',
'sections' => []
],
];

/**
* @var string
*/
public const CONST_PAGE_TOOLS = 'auth0_tools';

/**
* @var string
*/
Expand Down Expand Up @@ -415,6 +471,7 @@ final class Configuration extends Base
'auth0_ui_configuration' => 'renderConfiguration',
'auth0_ui_sync' => 'renderSyncConfiguration',
'auth0_ui_advanced' => 'renderAdvancedConfiguration',
'auth0_ui_tools' => 'renderToolsConfiguration',
];

public function onMenu(): void
Expand Down Expand Up @@ -463,6 +520,18 @@ public function onMenu(): void
},
position: $this->getPriority('MENU_POSITION_ADVANCED', 2, 'AUTH0_ADMIN'),
);

add_submenu_page(
parent_slug: 'auth0',
page_title: 'Auth0 — Tools',
menu_title: 'Tools',
capability: 'manage_options',
menu_slug: 'auth0_tools',
callback: static function (): void {
do_action('auth0_ui_tools');
},
position: $this->getPriority('MENU_POSITION_ADVANCED', 3, 'AUTH0_ADMIN'),
);
}

public function onSetup(): void
Expand Down Expand Up @@ -651,8 +720,15 @@ public function onUpdateAuthentication(?array $input): ?array
$sanitized = [
'pair_sessions' => Sanitize::integer((string) ($input['pair_sessions'] ?? 0), 2, 0) ?? 0,
'allow_fallback' => Sanitize::boolean((string) ($input['allow_fallback'] ?? '')) ?? '',
'fallback_secret' => Sanitize::string((string) ($input['fallback_secret'] ?? '')) ?? '',
];

if ($sanitized['fallback_secret'] === '') {
$sanitized['fallback_secret'] = bin2hex(random_bytes(64));
}

set_site_transient('auth0_updated_fallback', true, 60);

return array_filter($sanitized, static fn ($value): bool => '' !== $value);
}

Expand Down Expand Up @@ -905,6 +981,43 @@ public function onUpdateTokens(?array $input): ?array
return array_filter($sanitized, static fn ($value): bool => '' !== $value);
}

/**
* @param null|array<null|bool|int|string> $input
*
* @return null|array<mixed>
*/
public function onUpdateBackchannelLogout(?array $input): ?array
{
if (null === $input) {
return null;
}

$sanitized = [
'enabled' => Sanitize::string((string) ($input['secret'] ?? '')) ?? '',
'secret' => Sanitize::string((string) ($input['secret'] ?? '')) ?? '',
'ttl' => Sanitize::integer((string) ($input['ttl'] ?? 0), 2_592_000, 0) ?? 0,
];

if ($sanitized['secret'] === '') {
$sanitized['secret'] = bin2hex(random_bytes(64));
}

set_site_transient('auth0_updated_backchannel', true, 60);

return array_filter($sanitized, static fn ($value): bool => '' !== $value);
}

public function renderToolsConfiguration(): void
{
Render::pageBegin(self::PAGES[self::CONST_PAGE_TOOLS]['title']);

// settings_fields(self::CONST_PAGE_ADVANCED);
// do_settings_sections(self::CONST_PAGE_ADVANCED);
// submit_button();

Render::pageEnd();
}

public function renderAdvancedConfiguration(): void
{
Render::pageBegin(self::PAGES[self::CONST_PAGE_ADVANCED]['title']);
Expand Down Expand Up @@ -944,6 +1057,50 @@ private function getOptionDescription(string $context): string
return sprintf('Must include origin domain of <code>`%s`</code>', Sanitize::domain(site_url()) ?? '');
}

if ('fallback_secret' === $context) {
if ($this->isPluginReady()) {
$fallbackAllowed = $this->getPlugin()->getOption('authentication', 'allow_fallback', 0);

if (1 === $fallbackAllowed) {
$updated = get_site_transient('auth0_updated_fallback');

if (! $updated) {
return 'Save your changes to view your fallback URI. Erase the secret to generate a new one.';
} else {
delete_site_transient('auth0_updated_fallback');
}

$fallbackSecret = $this->getPlugin()->getOption('authentication', 'fallback_secret');

if (null !== $fallbackSecret) {
return sprintf('Your fallback URI is <code>`%s?auth0_fb=%s`</code>', wp_login_url(), $fallbackSecret);
}
}
}
}

if ('backchannel_logout_secret' === $context) {
if ($this->isPluginReady()) {
$backchannelLogoutEnabled = $this->getPlugin()->getOption('backchannel_logout', 'enabled', 0);

if (0 !== $backchannelLogoutEnabled) {
$updated = get_site_transient('auth0_updated_backchannel');

if (! $updated) {
return 'Save your changes to view your Back-Channel Logout URI. Erase the secret to generate a new one.';
} else {
delete_site_transient('auth0_updated_backchannel');
}

$backchannelLogoutSecret = $this->getPlugin()->getOption('backchannel_logout', 'secret');

if (null !== $backchannelLogoutSecret) {
return sprintf('Your Back-Channel Logout URI is <code>`%s?auth0_bcl=%s`</code>', wp_login_url(), $backchannelLogoutSecret);
}
}
}
}

if ('enable' === $context) {
if ($this->isPluginReady()) {
return 'Manage WordPress authentication with Auth0.';
Expand Down
9 changes: 9 additions & 0 deletions src/Actions/Tools.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Auth0\WordPress\Actions;

final class Tools extends Base
{
}
4 changes: 3 additions & 1 deletion src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Auth0\WordPress\Actions\Base as Actions;
use Auth0\WordPress\Actions\Configuration as ConfigurationActions;
use Auth0\WordPress\Actions\Sync as SyncActions;
use Auth0\WordPress\Actions\Tools as ToolsActions;
use Auth0\WordPress\Cache\WpObjectCachePool;
use Auth0\WordPress\Filters\Authentication as AuthenticationFilters;
use Auth0\WordPress\Filters\Base as Filters;
Expand All @@ -20,7 +21,7 @@ final class Plugin
/**
* @var array<class-string<Actions>>
*/
private const ACTIONS = [AuthenticationActions::class, ConfigurationActions::class, SyncActions::class];
private const ACTIONS = [AuthenticationActions::class, ConfigurationActions::class, SyncActions::class, ToolsActions::class];

/**
* @var array<class-string<Filters>>
Expand Down Expand Up @@ -309,6 +310,7 @@ private function importConfiguration(): SdkConfiguration
if ($caching !== 'disable') {
$wpObjectCachePool = new WpObjectCachePool();
$sdkConfiguration->setTokenCache($wpObjectCachePool);
$sdkConfiguration->setBackchannelLogoutCache($wpObjectCachePool);
}

return $sdkConfiguration;
Expand Down

0 comments on commit 35d05a6

Please sign in to comment.