diff --git a/openid_connect.routing.yml b/openid_connect.routing.yml
index f74ebfe30a19b82b7b976315944b3d8d9ba167b8..e5168d257b99586b63abc605ac80cab21b1153bd 100644
--- a/openid_connect.routing.yml
+++ b/openid_connect.routing.yml
@@ -84,6 +84,14 @@ openid_connect.logout:
     _csrf_token: 'TRUE'
   options:
     no_cache: TRUE
+    _csrf_confirm_form_route: 'openid_connect.logout.confirm'
+
+openid_connect.logout.confirm:
+  path: '/user/logout/confirm'
+  defaults:
+    _form: '\Drupal\openid_connect\Form\UserLogoutConfirmation'
+  requirements:
+    _user_is_logged_in: 'TRUE'
 
 openid_connect.login:
   path: '/user/login/openid_connect'
diff --git a/openid_connect.services.yml b/openid_connect.services.yml
index 52ca9b14bb5978d1921c86048d65e754bdf2ea6b..30d8258b0c1fb367711391a344ddfd25328008c3 100644
--- a/openid_connect.services.yml
+++ b/openid_connect.services.yml
@@ -43,3 +43,8 @@ services:
   openid_connect.autodiscover:
     class: Drupal\openid_connect\OpenIDConnectAutoDiscover
   Drupal\openid_connect\OpenIDConnectAutoDiscover: '@openid_connect.autodiscover'
+
+  Drupal\openid_connect\Service\LogoutService:
+    arguments:
+      # Wire arguments manually until externalauth provides autowiring aliases.
+      $authmap: '@externalauth.authmap'
diff --git a/src/Controller/OpenIDConnectRedirectController.php b/src/Controller/OpenIDConnectRedirectController.php
index 3271c542558c5222ff57d63114a30a0eb7b05ee5..0cbbd79ae98e54011b2733301c556f841f768ec1 100644
--- a/src/Controller/OpenIDConnectRedirectController.php
+++ b/src/Controller/OpenIDConnectRedirectController.php
@@ -12,7 +12,6 @@ use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Logger\LoggerChannelTrait;
 use Drupal\Core\Messenger\MessengerTrait;
 use Drupal\Core\Routing\Access\AccessInterface;
-use Drupal\Core\Routing\TrustedRedirectResponse;
 use Drupal\Core\Session\AccountProxyInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
@@ -23,6 +22,7 @@ use Drupal\openid_connect\OpenIDConnectClientEntityInterface;
 use Drupal\openid_connect\OpenIDConnectSessionInterface;
 use Drupal\openid_connect\OpenIDConnectStateTokenInterface;
 use Drupal\openid_connect\Plugin\OpenIDConnectClientInterface;
+use Drupal\openid_connect\Service\LogoutService;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -118,6 +118,13 @@ class OpenIDConnectRedirectController implements ContainerInjectionInterface, Ac
    */
   protected $claims;
 
+  /**
+   * The logout service.
+   *
+   * @var \Drupal\openid_connect\Service\LogoutService
+   */
+  protected $openIdLogoutService;
+
   /**
    * The constructor.
    *
@@ -143,8 +150,23 @@ class OpenIDConnectRedirectController implements ContainerInjectionInterface, Ac
    *   The entity type manager.
    * @param \Drupal\openid_connect\OpenIDConnectClaims $claims
    *   The OpenID claims service.
+   * @param \Drupal\openid_connect\Service\LogoutService $openid_logout_service
+   *   The logout service for openid connect.
    */
-  public function __construct(OpenIDConnect $openid_connect, OpenIDConnectStateTokenInterface $state_token, RequestStack $request_stack, OpenIDConnectSessionInterface $session, ConfigFactoryInterface $config_factory, AuthmapInterface $authmap, AccountProxyInterface $current_user, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, EntityTypeManagerInterface $entity_type_manager, OpenIDConnectClaims $claims) {
+  public function __construct(
+    OpenIDConnect $openid_connect,
+    OpenIDConnectStateTokenInterface $state_token,
+    RequestStack $request_stack,
+    OpenIDConnectSessionInterface $session,
+    ConfigFactoryInterface $config_factory,
+    AuthmapInterface $authmap,
+    AccountProxyInterface $current_user,
+    ModuleHandlerInterface $module_handler,
+    LanguageManagerInterface $language_manager,
+    EntityTypeManagerInterface $entity_type_manager,
+    OpenIDConnectClaims $claims,
+    LogoutService $openid_logout_service,
+  ) {
     $this->openIDConnect = $openid_connect;
     $this->stateToken = $state_token;
     $this->requestStack = $request_stack;
@@ -156,6 +178,7 @@ class OpenIDConnectRedirectController implements ContainerInjectionInterface, Ac
     $this->languageManager = $language_manager;
     $this->entityTypeManager = $entity_type_manager;
     $this->claims = $claims;
+    $this->openIdLogoutService = $openid_logout_service;
   }
 
   /**
@@ -173,7 +196,8 @@ class OpenIDConnectRedirectController implements ContainerInjectionInterface, Ac
       $container->get('module_handler'),
       $container->get('language_manager'),
       $container->get('entity_type.manager'),
-      $container->get('openid_connect.claims')
+      $container->get('openid_connect.claims'),
+      $container->get('Drupal\openid_connect\Service\LogoutService')
     );
   }
 
@@ -408,64 +432,9 @@ class OpenIDConnectRedirectController implements ContainerInjectionInterface, Ac
   /**
    * Redirect after logout.
    */
-  public function redirectLogout() {
-    // Set default URL.
-    $language = $this->languageManager->getCurrentLanguage();
-    $default_url = Url::fromRoute('<front>', [], ['language' => $language])->toString(TRUE);
-    $response = new RedirectResponse($default_url->getGeneratedUrl());
-
-    // @todo The fact that the user has a connected account doesn't necessarily
-    //   mean that it was used for the login. This info should probably be kept
-    //   in the session.
-    // Get client names for this user based on its username.
-    $mapped_users = $this->authmap->getAll($this->currentUser->id());
-    if (is_array($mapped_users) & !empty($mapped_users)) {
-      foreach (array_keys($mapped_users) as $key) {
-        // strlen('openid_connect.') = 15.
-        $client_name = substr($key, 15);
-
-        // Perform log out.
-        if (!empty($client_name)) {
-          /** @var \Drupal\openid_connect\Entity\OpenIDConnectClientEntity $entity */
-          $entity = current($this->entityTypeManager->getStorage('openid_connect_client')->loadByProperties(['id' => $client_name]));
-          if ($entity) {
-            $endpoints = $entity->getPlugin()->getEndpoints();
-
-            $config = $this->configFactory->get('openid_connect.settings');
-
-            $redirect_logout = $config->get('redirect_logout');
-            $redirect_logout_url = empty($redirect_logout) ? FALSE : Url::fromUri('internal:/' . ltrim($redirect_logout, '/'), ['language' => $language]);
-
-            // Destroy session if provider supports it.
-            $end_session_enabled = $config->get('end_session_enabled') ?? FALSE;
-            if ($end_session_enabled && !empty($endpoints['end_session'])) {
-              $url_options = [
-                'query' => ['id_token_hint' => $this->session->retrieveIdToken()],
-              ];
-              if ($redirect_logout_url) {
-                $url_options['query']['post_logout_redirect_uri'] = $redirect_logout_url->setAbsolute()->toString(TRUE)->getGeneratedUrl();
-              }
-              $redirect = Url::fromUri($endpoints['end_session'], $url_options)->toString(TRUE);
-              $response = new TrustedRedirectResponse($redirect->getGeneratedUrl());
-              $response->addCacheableDependency($redirect);
-            }
-            else {
-              if (!$end_session_enabled) {
-                $this->messenger()->addWarning($this->t('@provider does not support log out. You are logged out of this site but not out of the OpenID Connect provider.', ['@provider' => $entity->label()]));
-              }
-              if ($redirect_logout_url) {
-                $url = $redirect_logout_url->toString(TRUE)->getGeneratedUrl();
-                $response = new TrustedRedirectResponse($url);
-                $response->addCacheableDependency($url);
-              }
-            }
-            $rsp = ['response' => &$response];
-            $context = ['client' => $client_name];
-            $this->moduleHandler->alter('openid_connect_redirect_logout', $rsp, $context);
-          }
-        }
-      }
-    }
+  public function redirectLogout(): RedirectResponse {
+    // Get the expected redirect response.
+    $response = $this->openIdLogoutService->getLogoutRedirectResponse();
     // Logout from Drupal.
     user_logout();
     return $response;
diff --git a/src/Form/UserLogoutConfirmation.php b/src/Form/UserLogoutConfirmation.php
new file mode 100644
index 0000000000000000000000000000000000000000..8591df5ec643665253d0ee87236b180b2300ef11
--- /dev/null
+++ b/src/Form/UserLogoutConfirmation.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\openid_connect\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\openid_connect\Service\LogoutService;
+use Drupal\user\Form\UserLogoutConfirm;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provide a user logout confirmation form for OpenID logouts.
+ */
+final class UserLogoutConfirmation extends UserLogoutConfirm {
+
+  /**
+   * {@inheritDoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('Drupal\openid_connect\Service\LogoutService')
+    );
+  }
+
+  /**
+   * Constructor for the UserLogoutConfirmation form.
+   */
+  public function __construct(
+    protected readonly LogoutService $logoutService,
+  ) {}
+
+  /**
+   * {@inheritDoc}
+   */
+  public function getFormId(): string {
+    return 'openid_connect_user_logout';
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state): void {
+    // Get the response prior to logging the user out.
+    $response = $this->logoutService->getLogoutRedirectResponse();
+    user_logout();
+    // Get the expected OpenID Redirect.
+    $form_state->setResponse($response);
+  }
+
+}
diff --git a/src/Service/LogoutService.php b/src/Service/LogoutService.php
new file mode 100644
index 0000000000000000000000000000000000000000..0afe2cc39eb023ad6cba65f590ce4e874346ff35
--- /dev/null
+++ b/src/Service/LogoutService.php
@@ -0,0 +1,255 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\openid_connect\Service;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Routing\TrustedRedirectResponse;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Drupal\externalauth\AuthmapInterface;
+use Drupal\openid_connect\OpenIDConnectClientEntityInterface;
+use Drupal\openid_connect\OpenIDConnectSessionInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Handle custom logouts with OpenID Connect.
+ */
+class LogoutService {
+
+  use StringTranslationTrait;
+
+  /**
+   * Construct a logout service class.
+   */
+  public function __construct(
+    protected readonly ConfigFactoryInterface $configFactory,
+    protected readonly AuthmapInterface $authmap,
+    protected readonly AccountProxyInterface $currentUser,
+    protected readonly ModuleHandlerInterface $moduleHandler,
+    protected readonly LanguageManagerInterface $languageManager,
+    protected readonly EntityTypeManagerInterface $entityTypeManager,
+    protected readonly OpenIDConnectSessionInterface $session,
+    protected readonly LoggerChannelFactoryInterface $loggerChannelFactory,
+  ) {
+  }
+
+  /**
+   * Get the redirect response to be used when a member logs out.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   The redirect to the provider and/or the internal redirect.
+   */
+  public function getLogoutRedirectResponse(): RedirectResponse {
+    $response = $this->getDefaultResponse();
+    // If both end session and logout redirect are disabled, return the default.
+    if (
+      !$this->isEndSessionEnabled() &&
+      !$this->isLogoutRedirectEnabled()
+    ) {
+      return $response;
+    }
+
+    // If the user isn't mapped to any OpenID Clients, return the default.
+    if (!$this->hasMappedUsers()) {
+      return $response;
+    }
+
+    // Get the openid connect client used for the login.
+    $provider = $this->getLoginProvider();
+    // Guard the provider. If the user is not connected to OpenID
+    // then we want to default the logout.
+    if (is_null($provider)) {
+      return $response;
+    }
+    $logoutRedirectUrl = $this->getLogoutRedirectUrl();
+
+    // Default the response to the home page.
+    $response = new TrustedRedirectResponse('internal:/<front>');
+
+    // If the logout redirect is enabled, set the default redirect.
+    if ($this->isLogoutRedirectEnabled()) {
+      $redirectUrl = $logoutRedirectUrl->toString(TRUE)->getGeneratedUrl();
+      $response->setTrustedTargetUrl($redirectUrl);
+      $response->addCacheableDependency($redirectUrl);
+    }
+
+    if (
+      $this->isEndSessionEnabled() &&
+      $this->providerHasEndSessionEndpoint($provider)
+    ) {
+      // This will override the redirect only, which is expected.
+      $urlOptions = [
+        'query' => ['id_token_hint' => $this->session->retrieveIdToken()],
+      ];
+      if ($logoutRedirectUrl) {
+        $urlOptions['query']['post_logout_redirect_uri'] = $logoutRedirectUrl->setAbsolute()->toString(TRUE)->getGeneratedUrl();
+      }
+      $redirectUrl = Url::fromUri($this->getProviderEndSessionEndpoint($provider), $urlOptions)->toString(TRUE);
+      $response = new TrustedRedirectResponse($redirectUrl->getGeneratedUrl());
+      $response->addCacheableDependency($redirectUrl);
+    }
+
+    // If the end session is expected and the provider doesn't
+    // have an endpoint configured, write to the logs of a misconfiguration.
+    if (
+      $this->isEndSessionEnabled() &&
+      !$this->providerHasEndSessionEndpoint($provider)
+    ) {
+      // Alert the logs of a misconfiguration.
+      $this->loggerChannelFactory->get('openid_connect')->warning(
+        sprintf('%s does not support log out. Drupal session was expired, but the session at the identity provider remains.', $provider->label())
+      );
+    }
+
+    $clientName = $provider?->getPlugin()?->getPluginId() ?? 'unknown';
+
+    $rsp = ['response' => &$response];
+    $context = ['client' => $clientName];
+    $this->moduleHandler->alter('openid_connect_redirect_logout', $rsp, $context);
+
+    return $response;
+  }
+
+  /**
+   * Default any redirects to the home page.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   A default redirect response.
+   */
+  protected function getDefaultResponse(): RedirectResponse {
+    $language = $this->languageManager->getCurrentLanguage();
+    $default_url = Url::fromRoute('<front>', [], ['language' => $language])->toString(TRUE);
+    return new RedirectResponse($default_url->getGeneratedUrl());
+  }
+
+  /**
+   * Check if the redirect logout setting is enabled.
+   *
+   * @return bool
+   *   True if the redirect logout setting has a value.
+   */
+  protected function isLogoutRedirectEnabled(): bool {
+    return !empty($this->configFactory->get('openid_connect.settings')->get('redirect_logout'));
+  }
+
+  /**
+   * Get the redirect logout value as a Url.
+   *
+   * @return \Drupal\Core\Url|null
+   *   The redirect logout value transformed as a Url object.
+   */
+  protected function getLogoutRedirectUrl(): ?Url {
+    $redirectLogout = $this->configFactory->get('openid_connect.settings')->get('redirect_logout');
+    if (empty($redirectLogout)) {
+      return NULL;
+    }
+
+    return Url::fromUri(sprintf('internal:/%s', ltrim($redirectLogout, '/')), ['language' => $this->languageManager->getCurrentLanguage()]);
+  }
+
+  /**
+   * Check if the end session from provider setting is enabled.
+   *
+   * @return bool
+   *   True if the end session configuration is enabled.
+   */
+  protected function isEndSessionEnabled(): bool {
+    return $this->configFactory->get('openid_connect.settings')->get('end_session_enabled') ?? FALSE;
+  }
+
+  /**
+   * Check if the provider has an end session endpoint defined.
+   *
+   * @param \Drupal\openid_connect\OpenIDConnectClientEntityInterface $provider
+   *   The open id provider to check for an end session endpoint.
+   *
+   * @return bool
+   *   True if the provider has a value defined for the end session endpoint.
+   */
+  protected function providerHasEndSessionEndpoint(OpenIDConnectClientEntityInterface $provider): bool {
+    // Pull the end_session endpoint from the endpoint array.
+    ['end_session' => $end_session_endpoint] = $provider->getPlugin()->getEndpoints();
+    return !empty($end_session_endpoint);
+  }
+
+  /**
+   * Get the end session endpoint from the provider.
+   *
+   * @param \Drupal\openid_connect\OpenIDConnectClientEntityInterface $provider
+   *   The open id provider to retrieve an end session endpoint.
+   *
+   * @return string|null
+   *   The endpoint if defined, otherwise null.
+   */
+  protected function getProviderEndSessionEndpoint(OpenIDConnectClientEntityInterface $provider): ?string {
+    // Pull the end_session endpoint from the endpoints array.
+    ['end_session' => $end_session_endpoint] = $provider->getPlugin()->getEndpoints();
+    // Return the endpoint if available.
+    return !empty($end_session_endpoint) ? $end_session_endpoint : NULL;
+  }
+
+  /**
+   * Does openid connect have mapped data for the currently logged in user.
+   *
+   * @return bool
+   *   True if the user has logged in with an openid connect client.
+   */
+  protected function hasMappedUsers(): bool {
+    $test = $this->getMappedUsers();
+    return !empty($test);
+  }
+
+  /**
+   * Get all openid connect user mappings for the logged in user.
+   *
+   * @return array
+   *   All openid connect user mappings for the logged in user.
+   */
+  protected function getMappedUsers(): array {
+    return $this->authmap->getAll($this->currentUser->id());
+  }
+
+  /**
+   * Get the assumed provider that the user logged in with.
+   *
+   * @return \Drupal\openid_connect\OpenIDConnectClientEntityInterface|null
+   *   The openid connect provider or null if not found.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  protected function getLoginProvider(): ?OpenIDConnectClientEntityInterface {
+    // @todo The fact that the user has a connected account doesn't necessarily
+    // mean that it was used for the login. This info should probably be kept
+    // in the session.
+    $provider = NULL;
+    foreach (array_keys($this->getMappedUsers()) as $mappedUserKey) {
+      // Removing the 'openid_connect.' prefix (which is 15 characters long)
+      // This will provide the client name as it was stored in the
+      // external authmap table.
+      $client_name = substr($mappedUserKey, 15);
+      if (empty($client_name)) {
+        continue;
+      }
+      $entities = $this->entityTypeManager
+        ->getStorage('openid_connect_client')
+        ->loadByProperties(['id' => $client_name]);
+
+      // If there is a provider, set it and break the loop.
+      if (!empty($entities)) {
+        $provider = current($entities);
+        break;
+      }
+    }
+
+    return $provider;
+  }
+
+}
diff --git a/tests/src/Functional/AutoLoginTest.php b/tests/src/Functional/AutoLoginTest.php
index a6c5151ca460df9cd77e7fea6cb302d7935f2534..dced8c1851c81b86592f713823e36af44511f7a3 100644
--- a/tests/src/Functional/AutoLoginTest.php
+++ b/tests/src/Functional/AutoLoginTest.php
@@ -4,15 +4,12 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\openid_connect\Functional;
 
-use Drupal\Core\Url;
-use Drupal\Tests\BrowserTestBase;
-
 /**
  * Tests the auto login process.
  *
  * @group openid_connect
  */
-class AutoLoginTest extends BrowserTestBase {
+class AutoLoginTest extends OpenIdConnectTestBase {
 
   const OIDC_LABEL = 'Label For OIDC Client';
 
@@ -101,19 +98,4 @@ class AutoLoginTest extends BrowserTestBase {
       ->addressEquals('https://example.com/oauth2/authorize');
   }
 
-  /**
-   * Override the drupalLogout() method.
-   *
-   * Normal logout field validation breaks when the autostart
-   * setting is enabled. This override removes those assertions.
-   */
-  protected function drupalLogout(): void {
-    $destination = Url::fromRoute('user.page')->toString();
-    $this->drupalGet(Url::fromRoute('user.logout.confirm', options: ['query' => ['destination' => $destination]]));
-    // Target the submit button using the name rather than the value to work
-    // regardless of the user interface language.
-    $this->submitForm([], 'op', 'user-logout-confirm');
-    $this->drupalResetSession();
-  }
-
 }
diff --git a/tests/src/Functional/LogoutUserTest.php b/tests/src/Functional/LogoutUserTest.php
index b3525957b52930eee416cbf27bc7a712b2cd123a..63497cc06a3e6e0462a6d1a0c015b0b7b8ff292d 100644
--- a/tests/src/Functional/LogoutUserTest.php
+++ b/tests/src/Functional/LogoutUserTest.php
@@ -16,11 +16,14 @@ class LogoutUserTest extends BrowserTestBase {
 
   use OpenIdClientTestTrait;
 
+  const CLIENT_ID = 'test';
+
   /**
    * {@inheritdoc}
    */
   protected static $modules = [
     'openid_connect',
+    'externalauth',
     'user',
     'block',
   ];
@@ -30,37 +33,153 @@ class LogoutUserTest extends BrowserTestBase {
    */
   protected $defaultTheme = 'stark';
 
+  /**
+   * The logout url.
+   *
+   * @var \Drupal\Core\Url
+   */
+  protected Url $logoutUrl;
+
+  /**
+   * The string representation of the logout url.
+   *
+   * @var string
+   */
+  protected string $logoutUrlPlain;
+
+  /**
+   * The confirmation url.
+   *
+   * @var \Drupal\Core\Url
+   */
+  protected Url $logoutConfirmUrl;
+
+  /**
+   * The test client.
+   *
+   * @var \Drupal\openid_connect\OpenIDConnectClientEntityInterface
+   */
+  protected $openIdConnectClient;
+
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->createTestClient('test', 'Test OIDC Client');
+    $this->openIdConnectClient = $this->createTestClient(self::CLIENT_ID, 'Test OIDC Client');
     $this->placeBlock('system_menu_block:account');
+    $this->placeBlock('system_messages_block');
+    $this->logoutUrlPlain = '/user/logout';
+    $this->logoutUrl = Url::fromRoute('user.logout');
+    $this->logoutConfirmUrl = Url::fromRoute('openid_connect.logout.confirm');
   }
 
   /**
    * Confirm CSRF token is required to log out.
    */
   public function testCsrfOnLogout(): void {
-    $logoutUrl = Url::fromRoute('user.logout');
+    $this->toggleEndSessionSetting(FALSE);
     $account = $this->createUser();
     $this->drupalLogin($account);
 
     // Test missing csrf token does not log the user out.
-    $this->drupalGet($logoutUrl);
+    // Assert that the user is shown the confirmation form instead.
+    $this->drupalGet($this->logoutUrlPlain);
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals($this->logoutConfirmUrl);
+    // Confirm the cancel link does not logout the user.
+    $this->clickLink('Cancel');
     $this->assertTrue($this->drupalUserIsLoggedIn($account));
-    $this->assertSession()->statusCodeEquals(403);
 
     // Test invalid csrf token does not log the user out.
-    $this->drupalGet($logoutUrl, ['query' => ['token' => '123']]);
+    // Also confirm the confirmation form is shown instead.
+    $this->drupalGet($this->logoutUrl, ['query' => ['token' => '123']]);
     $this->assertTrue($this->drupalUserIsLoggedIn($account));
-    $this->assertSession()->statusCodeEquals(403);
+    $this->assertSession()->statusCodeEquals(200);
+    // Assert the confirmation form is shown.
+    $this->assertSession()->addressEquals($this->logoutConfirmUrl);
+    // Test the confirmation form.
+    $this->submitForm([], 'Log out');
+    $this->assertFalse($this->drupalUserIsLoggedIn($account));
 
-    // Test with valid logout link.
+    // Test with a valid logout link.
+    $this->drupalResetSession();
+    $this->drupalLogin($account);
     $this->drupalGet('user');
     $this->getSession()->getPage()->clickLink('Log out');
     $this->assertFalse($this->drupalUserIsLoggedIn($account));
   }
 
+  /**
+   * Test the OpenID Redirect settings with the logout confirmation forms.
+   */
+  public function testOpenIdRedirectOnLogout(): void {
+    // Enable end session logout redirect.
+    $this->toggleEndSessionSetting(TRUE);
+    // Get the endpoint of the client for assertions.
+    $endpoints = $this->openIdConnectClient->getPlugin()->getEndpoints();
+    $account = $this->createUser();
+    // Link the account to the external auth table.
+    $authmap = \Drupal::service('externalauth.authmap');
+    $authmap->save($account, sprintf('openid_connect.%s', self::CLIENT_ID), $this->randomMachineName());
+
+    // Confirm a valid logout redirects to the end session endpoint.
+    $this->drupalLogin($account);
+    $this->drupalGet('user');
+    $this->getSession()->getPage()->clickLink('Log out');
+    $this->assertSession()->addressEquals($endpoints['end_session']);
+    $this->assertFalse($this->drupalUserIsLoggedIn($account));
+    $this->drupalResetSession();
+
+    // Confirm the logout confirmation form
+    // redirects to the end session endpoint.
+    $this->drupalLogin($account);
+    $this->drupalGet($this->logoutUrlPlain);
+    $this->assertSession()->statusCodeEquals(200);
+    // Assert the confirmation form is shown.
+    $this->assertSession()->addressEquals($this->logoutConfirmUrl);
+    // Test the confirmation form.
+    $this->submitForm([], 'Log out');
+    $this->assertSession()->addressEquals($endpoints['end_session']);
+    $this->assertFalse($this->drupalUserIsLoggedIn($account));
+    $this->drupalResetSession();
+
+    // Confirm the regular logout redirect is working.
+    $this->toggleEndSessionSetting(FALSE);
+    $this->setRedirectLogoutUrl('/path/to/redirect');
+    $this->drupalLogin($account);
+    $this->drupalGet('user');
+    $this->getSession()->getPage()->clickLink('Log out');
+    $this->assertSession()->addressEquals('/path/to/redirect');
+    $this->assertFalse($this->drupalUserIsLoggedIn($account));
+    $this->drupalResetSession();
+
+    // Confirm the end session without an endpoint goes to the redirect url.
+    $client = $this->getTestClient(self::CLIENT_ID);
+    $this->assertNotNull($client);
+    $plugin = $client->getPlugin();
+    $clientConfig = $plugin->getConfiguration();
+    $clientConfig['end_session_endpoint'] = '';
+    $plugin->setConfiguration($clientConfig);
+    $client->save();
+    $this->toggleEndSessionSetting(TRUE);
+    $this->setRedirectLogoutUrl('/path/to/different/redirect');
+
+    $this->drupalLogin($account);
+    $this->drupalGet('user');
+    $this->getSession()->getPage()->clickLink('Log out');
+    $this->assertSession()->addressEquals('/path/to/different/redirect');
+    $this->assertFalse($this->drupalUserIsLoggedIn($account));
+    $this->drupalResetSession();
+
+    // Confirm empty redirects go to the home page.
+    $this->toggleEndSessionSetting(FALSE);
+    // Set the redirect to an empty string.
+    $this->setRedirectLogoutUrl('');
+    $this->drupalLogin($account);
+    $this->drupalGet('user');
+    $this->getSession()->getPage()->clickLink('Log out');
+    $this->assertSession()->addressEquals(Url::fromRoute('<front>'));
+  }
+
 }
diff --git a/tests/src/Functional/OpenIdClientTestTrait.php b/tests/src/Functional/OpenIdClientTestTrait.php
index 546f1b8b8b9265b8e9184116fa5270ccf7db4727..18e00e22f2cb8c8ff495e192c1868194193ae783 100644
--- a/tests/src/Functional/OpenIdClientTestTrait.php
+++ b/tests/src/Functional/OpenIdClientTestTrait.php
@@ -31,16 +31,15 @@ trait OpenIdClientTestTrait {
         'id' => $clientId,
         'label' => $clientLabel,
         'plugin' => 'generic',
-        'redirect_uri' => 'http://localhost',
-        'grant_type' => 'authorization_code',
-        'response_type' => 'code',
-        'authorization_endpoint' => 'http://localhost/authorize',
-        'token_endpoint' => 'http://localhost/token',
-        'userinfo_endpoint' => 'http://localhost/userinfo',
-        'jwks_uri' => 'http://localhost/jwks',
-        'scopes' => ['openid email'],
-        'client_secret' => 'test',
         'status' => TRUE,
+        'settings' => [
+          'authorization_endpoint' => 'http://localhost/authorize',
+          'token_endpoint' => 'http://localhost/token',
+          'userinfo_endpoint' => 'http://localhost/userinfo',
+          'end_session_endpoint' => 'http://localhost/endsession',
+          'scopes' => ['openid email'],
+          'client_secret' => 'test',
+        ],
       ]
     );
     $client->save();
@@ -48,4 +47,44 @@ trait OpenIdClientTestTrait {
     return $client;
   }
 
+  /**
+   * Retrieve a test client.
+   *
+   * @return \Drupal\openid_connect\OpenIDConnectClientEntityInterface|null
+   *   The test client.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function getTestClient(
+    string $clientId,
+  ): OpenIDConnectClientEntityInterface {
+    return \Drupal::service('entity_type.manager')
+      ->getStorage('openid_connect_client')->load($clientId);
+  }
+
+  /**
+   * Set the `redirect_logout` OpenID Setting.
+   *
+   * @param string $path
+   *   The redirect path.
+   */
+  public function setRedirectLogoutUrl(string $path): void {
+    $settingsConfig = \Drupal::configFactory()->getEditable('openid_connect.settings');
+    $settingsConfig->set('redirect_logout', $path);
+    $settingsConfig->save();
+  }
+
+  /**
+   * Toggle the end session configuration setting.
+   *
+   * @param bool $enabled
+   *   True for enabled, false for off.
+   */
+  public function toggleEndSessionSetting(bool $enabled): void {
+    // Enable the end session endpoint.
+    $settingsConfig = \Drupal::configFactory()->getEditable('openid_connect.settings');
+    $settingsConfig->set('end_session_enabled', $enabled);
+    $settingsConfig->save();
+  }
+
 }
diff --git a/tests/src/Functional/OpenIdConnectTestBase.php b/tests/src/Functional/OpenIdConnectTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4421771c872303e9d782005f0d626a8ad1d3fbba
--- /dev/null
+++ b/tests/src/Functional/OpenIdConnectTestBase.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\openid_connect\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Common testing traits required for OpenID Connect.
+ */
+abstract class OpenIdConnectTestBase extends BrowserTestBase {
+
+  /**
+   * Override the drupalLogout() method.
+   *
+   * Normal logout field validation breaks when the autostart
+   * setting is enabled. This override removes those assertions.
+   */
+  public function drupalLogout(): void {
+    $destination = Url::fromRoute('user.page')->toString();
+    $this->drupalGet(Url::fromRoute('user.logout.confirm', options: ['query' => ['destination' => $destination]]));
+    // Target the submit button using the name rather than the value to work
+    // regardless of the user interface language.
+    $this->submitForm([], 'op', 'openid-connect-user-logout');
+    $this->drupalResetSession();
+  }
+
+}
diff --git a/tests/src/Functional/OpenIdConnectUiTest.php b/tests/src/Functional/OpenIdConnectUiTest.php
index 47e70a21962509e20ba3956d97df0783ea58506c..2c7f3e845b570e85290d2322cd136a00bbc0264e 100644
--- a/tests/src/Functional/OpenIdConnectUiTest.php
+++ b/tests/src/Functional/OpenIdConnectUiTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\openid_connect\Functional;
 
-use Drupal\Tests\BrowserTestBase;
-
 /**
  * Functional test for openid connect clients.
  *
@@ -11,7 +9,7 @@ use Drupal\Tests\BrowserTestBase;
  *
  * @group openid_connect
  */
-class OpenIdConnectUiTest extends BrowserTestBase {
+class OpenIdConnectUiTest extends OpenIdConnectTestBase {
 
   use OpenIdClientTestTrait;
 
@@ -105,6 +103,7 @@ class OpenIdConnectUiTest extends BrowserTestBase {
     $this->assertSession()->fieldNotExists('Password');
     // Logout as the administrator.
     $this->drupalLogout();
+    $this->drupalResetSession();
 
     // Login as a new administrator who can manage their own password.
     $newAdmin = $this->createUser([
@@ -119,6 +118,7 @@ class OpenIdConnectUiTest extends BrowserTestBase {
     $this->assertSession()->fieldExists('Password');
     // Logout as the new administrator.
     $this->drupalLogout();
+    $this->drupalResetSession();
 
     // Login as the normal user and confirm they can see their
     // password field.
@@ -134,6 +134,7 @@ class OpenIdConnectUiTest extends BrowserTestBase {
     $this->assertSession()->statusCodeEquals(200);
     $this->assertSession()->fieldNotExists('Password');
     $this->drupalLogout();
+    $this->drupalResetSession();
 
     // Link a new account with permission to set a password.
     // Confirm they are able to see the password field.
@@ -144,7 +145,7 @@ class OpenIdConnectUiTest extends BrowserTestBase {
     $this->assertSession()->statusCodeEquals(200);
     $this->assertSession()->fieldExists('Password');
     $this->drupalLogout();
-
+    $this->drupalResetSession();
   }
 
 }