diff --git a/composer.json b/composer.json
index 3a17860c5888464a6ffe3736fe6716212864e3a5..a76637dfa2a34811d4f4242c4a2ea1d480f927c9 100644
--- a/composer.json
+++ b/composer.json
@@ -14,5 +14,11 @@
     "php": ">=8.1.0",
     "ext-json": "*",
     "drupal/externalauth": "^2.0"
+  },
+  "require-dev": {
+    "drupal/key": "1.x-dev"
+  },
+  "suggest": {
+    "drupal/key": "Allows better key management"
   }
 }
diff --git a/config/schema/openid_connect.schema.yml b/config/schema/openid_connect.schema.yml
index 607549408b6532e402b7eab1b083f23b6d86e546..0d340c421c76c8ec25d602813e3eaa1e11a21fd1 100644
--- a/config/schema/openid_connect.schema.yml
+++ b/config/schema/openid_connect.schema.yml
@@ -59,12 +59,18 @@ openid_connect.client.plugin.*:
   type: mapping
   label: 'OpenID Connect plugin base settings'
   mapping: &base
+    credentials_provider:
+      type: string
+      label: 'Defines mechanism to provide credentials'
     client_id:
       type: string
       label: 'Client ID'
     client_secret:
       type: string
       label: 'Client secret'
+    credentials_key:
+      type: string
+      label: 'Reference to key providing credentials'
     iss_allowed_domains:
       type: string
       label: 'Domains that are allowed to initiate SSO using ISS'
diff --git a/openid_connect.install b/openid_connect.install
index fa82053b1fde9e84d2df97539f7d98bd877980c6..b72b2fe5dce05ec46db363665494587bb65239fa 100644
--- a/openid_connect.install
+++ b/openid_connect.install
@@ -388,3 +388,19 @@ function openid_connect_update_30004() {
     }
   }
 }
+
+/**
+ * Add extra credentials configuration to plugins.
+ */
+function openid_connect_update_30005() {
+  $storage = \Drupal::entityTypeManager()->getStorage('openid_connect_client');
+  $clients = $storage->loadMultiple();
+  foreach ($clients as $client) {
+    $config = $client->getPlugin()->getConfiguration();
+    if (!array_key_exists('credentials_provider', $config)) {
+      $client->getPlugin()->setConfiguration(['credentials_provider' => 'config']);
+      $client->getPlugin()->setConfiguration(['credentials_key' => '']);
+      $client->save();
+    }
+  }
+}
diff --git a/src/Plugin/OpenIDConnectClientBase.php b/src/Plugin/OpenIDConnectClientBase.php
index 28167024d49748918086723610066e000c733e7b..b4db01d9b63b5ffafbb77de0f28529287dc7139c 100644
--- a/src/Plugin/OpenIDConnectClientBase.php
+++ b/src/Plugin/OpenIDConnectClientBase.php
@@ -16,6 +16,7 @@ use Drupal\Core\Plugin\PluginWithFormsTrait;
 use Drupal\Core\Routing\TrustedRedirectResponse;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
+use Drupal\key\KeyRepository;
 use Drupal\openid_connect\OpenIDConnectAutoDiscover;
 use Drupal\openid_connect\OpenIDConnectStateTokenInterface;
 use GuzzleHttp\ClientInterface;
@@ -94,6 +95,13 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    */
   protected $parentEntityId;
 
+  /**
+   * The key repository service.
+   *
+   * @var \Drupal\key\KeyRepository|null
+   */
+  protected ?KeyRepository $keyRepository = NULL;
+
   /**
    * The constructor.
    *
@@ -119,8 +127,10 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    *   The OpenID state token service.
    * @param \Drupal\openid_connect\OpenIDConnectAutoDiscover $auto_discover
    *   The OpenID well-known discovery service.
+   * @param \Drupal\key\KeyRepository|null $key_repository
+   *   The key repository service.
    */
-  public function __construct(array $configuration, string $plugin_id, $plugin_definition, RequestStack $request_stack, ClientInterface $http_client, LoggerChannelFactoryInterface $logger_factory, TimeInterface $datetime_time, KillSwitch $page_cache_kill_switch, LanguageManagerInterface $language_manager, OpenIDConnectStateTokenInterface $state_token, OpenIDConnectAutoDiscover $auto_discover) {
+  public function __construct(array $configuration, string $plugin_id, $plugin_definition, RequestStack $request_stack, ClientInterface $http_client, LoggerChannelFactoryInterface $logger_factory, TimeInterface $datetime_time, KillSwitch $page_cache_kill_switch, LanguageManagerInterface $language_manager, OpenIDConnectStateTokenInterface $state_token, OpenIDConnectAutoDiscover $auto_discover, ?KeyRepository $key_repository = NULL) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->requestStack = $request_stack;
@@ -132,6 +142,7 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
     $this->stateToken = $state_token;
     $this->autoDiscover = $auto_discover;
     $this->parentEntityId = '';
+    $this->keyRepository = $key_repository;
     $this->setConfiguration($configuration);
   }
 
@@ -139,6 +150,13 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = $container->get('module_handler');
+    $keyRepository = NULL;
+    if ($module_handler->moduleExists('key')) {
+      $keyRepository = $container->get('key.repository');
+    }
+
     return new static(
       $configuration,
       $plugin_id,
@@ -150,7 +168,8 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
       $container->get('page_cache_kill_switch'),
       $container->get('language_manager'),
       $container->get('openid_connect.state_token'),
-      $container->get('openid_connect.autodiscover')
+      $container->get('openid_connect.autodiscover'),
+      $keyRepository
     );
   }
 
@@ -196,8 +215,10 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    */
   public function defaultConfiguration(): array {
     return [
+      'credentials_provider' => 'config',
       'client_id' => '',
       'client_secret' => '',
+      'credentials_key' => '',
       'iss_allowed_domains' => '',
     ];
   }
@@ -227,18 +248,68 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
+    $form['credentials_provider'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Credentials provider'),
+      '#options' => [
+        'config' => $this->t('Local configuration'),
+      ],
+      '#default_value' => $this->configuration['credentials_provider'] ?? 'config',
+    ];
+    // Enable key as credentials provider if key module is installed.
+    if ($this->keyRepository !== NULL) {
+      $form['credentials_provider']['#options']['key'] = $this->t('Key');
+    }
+
     $form['client_id'] = [
       '#title' => $this->t('Client ID'),
       '#type' => 'textfield',
       '#default_value' => $this->configuration['client_id'],
       '#required' => TRUE,
+      '#states' => [
+        'visible' => [
+          ':input[name="settings[credentials_provider]"]' => ['value' => 'config'],
+        ],
+        'required' => [
+          ':input[name="settings[credentials_provider]"]' => ['value' => 'config'],
+        ],
+      ],
     ];
+
     $form['client_secret'] = [
       '#title' => $this->t('Client secret'),
       '#type' => 'textarea',
       '#default_value' => $this->configuration['client_secret'],
       '#required' => TRUE,
+      '#states' => [
+        'visible' => [
+          ':input[name="settings[credentials_provider]"]' => ['value' => 'config'],
+        ],
+        'required' => [
+          ':input[name="settings[credentials_provider]"]' => ['value' => 'config'],
+        ],
+      ],
     ];
+
+    if ($this->keyRepository !== NULL) {
+      $form['credentials_key'] = [
+        '#type' => 'key_select',
+        '#title' => $this->t('Key'),
+        '#description' => $this->t('The key should contain the OAuth credentials. Choose key type "Authentication (Multivalue)", and add the properties "client_id" and "client_secret" to the key value.') . $this->t('<br />Example key data: <code>{"client_id": "47012495342576573474", "client_secret": "51106350561134283737"}</code>'),
+        '#default_value' => $this->configuration['credentials_key'] ?? '',
+        '#empty_option' => $this->t('- Please select -'),
+        '#key_filters' => ['type' => 'authentication_multivalue'],
+        '#states' => [
+          'visible' => [
+            ':input[name="settings[credentials_provider]"]' => ['value' => 'key'],
+          ],
+          'required' => [
+            ':input[name="settings[credentials_provider]"]' => ['value' => 'key'],
+          ],
+        ],
+      ];
+    }
+
     $form['iss_allowed_domains'] = [
       '#title' => $this->t('Allowed domains'),
       '#description' => $this->t('Enter one domain per line that are allowed to initiate SSO using ISS.'),
@@ -266,7 +337,22 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
    * {@inheritdoc}
    */
   public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
-    // Empty function. Can be overridden by derived classes if required.
+    // Manipulate client credentials storage.
+    // Method can be overridden by derived classes if required, to perform
+    // additional configuration actions.
+    $configuration = $form_state->getValues();
+    // Only save the relevant client credentials.
+    $credentials_provider = $configuration['credentials_provider'];
+    if ($credentials_provider === 'config') {
+      // Make sure no key reference is saved, if we are using config to store
+      // credentials.
+      $this->setConfiguration(['credentials_key' => '']);
+    }
+    elseif ($credentials_provider === 'key') {
+      // Make sure no client ID / secret is saved, if we are using keys to store
+      // credentials.
+      $this->setConfiguration(['client_id' => '', 'client_secret' => '']);
+    }
   }
 
   /**
@@ -295,6 +381,32 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
     return $response;
   }
 
+  /**
+   * Helper function to retrieve client ID / secret credentials.
+   *
+   * @return array
+   *   Array with 'client_id' and 'client_secret' keys.
+   */
+  protected function getClientCredentials(): array {
+    $credentials_provider = $this->configuration['credentials_provider'];
+    $client_id = $client_secret = '';
+    if ($credentials_provider === 'config') {
+      $client_id = $this->configuration['client_id'];
+      $client_secret = $this->configuration['client_secret'];
+    }
+    elseif ($this->keyRepository && $credentials_provider === 'key') {
+      $key_credentials = $this->keyRepository->getKey($this->configuration['credentials_key'])?->getKeyValues();
+      if (!empty($key_credentials)) {
+        $client_id = $key_credentials['client_id'] ?? '';
+        $client_secret = $key_credentials['client_secret'] ?? '';
+      }
+    }
+    return [
+      'client_id' => $client_id,
+      'client_secret' => $client_secret,
+    ];
+  }
+
   /**
    * Helper function for URL options.
    *
@@ -309,7 +421,7 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
   protected function getUrlOptions(string $scope, GeneratedUrl $redirect_uri): array {
     return [
       'query' => [
-        'client_id' => $this->configuration['client_id'],
+        'client_id' => $this->getClientCredentials()['client_id'],
         'response_type' => 'code',
         'scope' => $scope,
         'redirect_uri' => $redirect_uri->getGeneratedUrl(),
@@ -333,8 +445,8 @@ abstract class OpenIDConnectClientBase extends PluginBase implements OpenIDConne
     return [
       'form_params' => [
         'code' => $authorization_code,
-        'client_id' => $this->configuration['client_id'],
-        'client_secret' => $this->configuration['client_secret'],
+        'client_id' => $this->getClientCredentials()['client_id'],
+        'client_secret' => $this->getClientCredentials()['client_secret'],
         'redirect_uri' => $redirect_uri,
         'grant_type' => 'authorization_code',
       ],
diff --git a/tests/src/Functional/AutoLoginTest.php b/tests/src/Functional/AutoLoginTest.php
index dced8c1851c81b86592f713823e36af44511f7a3..dbe46294f37590d7c98eefe9aa99358768330fd2 100644
--- a/tests/src/Functional/AutoLoginTest.php
+++ b/tests/src/Functional/AutoLoginTest.php
@@ -41,6 +41,7 @@ class AutoLoginTest extends OpenIdConnectTestBase {
       [
         'label' => self::OIDC_LABEL,
         'id' => self::OIDC_ID,
+        'settings[credentials_provider]' => 'config',
         'settings[client_id]' => $this->randomString(8),
         'settings[client_secret]' => $this->randomString(8),
       ],
diff --git a/tests/src/Functional/Plugin/OpenIDConnectClient/GenericClientTest.php b/tests/src/Functional/Plugin/OpenIDConnectClient/GenericClientTest.php
index 5bb9793ad152082307111e50f039a59f9fc46f48..d867c578b315104e0b8a0499700dec2e86aa7353 100644
--- a/tests/src/Functional/Plugin/OpenIDConnectClient/GenericClientTest.php
+++ b/tests/src/Functional/Plugin/OpenIDConnectClient/GenericClientTest.php
@@ -68,6 +68,7 @@ class GenericClientTest extends BrowserTestBase {
     $edit = [
       'label' => self::LABEL,
       'id' => self::ID,
+      'settings[credentials_provider]' => 'config',
       'settings[client_id]' => $this->randomString(6),
       'settings[client_secret]' => $this->randomString(6),
       'settings[iss_allowed_domains]' => '',
diff --git a/tests/src/Unit/Entity/OpenIDConnectClientEntityTest.php b/tests/src/Unit/Entity/OpenIDConnectClientEntityTest.php
index c5e1589f34c2af1f1445803d986c58354ac5ac07..f4b793e9e911fe0c06e5d4e94c1cb86156757cb6 100644
--- a/tests/src/Unit/Entity/OpenIDConnectClientEntityTest.php
+++ b/tests/src/Unit/Entity/OpenIDConnectClientEntityTest.php
@@ -23,6 +23,8 @@ use Drupal\Tests\UnitTestCase;
  * @group openid_connect
  */
 class OpenIDConnectClientEntityTest extends UnitTestCase {
+
+  const CREDENTIALS_PROVIDER = 'config';
   const CLIENT_ID = 'test_client_id';
   const CLIENT_SECRET = 'test_client_secret';
   const PLUGIN_ID = 'generic';
@@ -31,6 +33,7 @@ class OpenIDConnectClientEntityTest extends UnitTestCase {
     'label' => 'Test Plugin',
     'plugin' => self::PLUGIN_ID,
     'settings' => [
+      'credentials_provider' => self::CREDENTIALS_PROVIDER,
       'client_id' => self::CLIENT_ID,
       'client_secret' => self::CLIENT_SECRET,
       'issuer_url' => '',