diff --git a/app/Auth/Access/Oidc/OidcIdToken.php b/app/Auth/Access/Oidc/OidcIdToken.php
index c955c3b09..ca2c85d09 100644
--- a/app/Auth/Access/Oidc/OidcIdToken.php
+++ b/app/Auth/Access/Oidc/OidcIdToken.php
@@ -4,35 +4,16 @@ namespace BookStack\Auth\Access\Oidc;
 
 class OidcIdToken
 {
-    /**
-     * @var array
-     */
-    protected $header;
-
-    /**
-     * @var array
-     */
-    protected $payload;
-
-    /**
-     * @var string
-     */
-    protected $signature;
+    protected array $header;
+    protected array $payload;
+    protected string $signature;
+    protected string $issuer;
+    protected array $tokenParts = [];
 
     /**
      * @var array[]|string[]
      */
-    protected $keys;
-
-    /**
-     * @var string
-     */
-    protected $issuer;
-
-    /**
-     * @var array
-     */
-    protected $tokenParts = [];
+    protected array $keys;
 
     public function __construct(string $token, string $issuer, array $keys)
     {
@@ -106,6 +87,14 @@ class OidcIdToken
         return $this->payload;
     }
 
+    /**
+     * Replace the existing claim data of this token with that provided.
+     */
+    public function replaceClaims(array $claims): void
+    {
+        $this->payload = $claims;
+    }
+
     /**
      * Validate the structure of the given token and ensure we have the required pieces.
      * As per https://datatracker.ietf.org/doc/html/rfc7519#section-7.2.
diff --git a/app/Auth/Access/Oidc/OidcService.php b/app/Auth/Access/Oidc/OidcService.php
index 1ca5e19a2..3da8b76eb 100644
--- a/app/Auth/Access/Oidc/OidcService.php
+++ b/app/Auth/Access/Oidc/OidcService.php
@@ -9,6 +9,8 @@ use BookStack\Auth\User;
 use BookStack\Exceptions\JsonDebugException;
 use BookStack\Exceptions\StoppedAuthenticationException;
 use BookStack\Exceptions\UserRegistrationException;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Facades\Cache;
 use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@@ -21,24 +23,12 @@ use Psr\Http\Client\ClientInterface as HttpClient;
  */
 class OidcService
 {
-    protected RegistrationService $registrationService;
-    protected LoginService $loginService;
-    protected HttpClient $httpClient;
-    protected GroupSyncService $groupService;
-
-    /**
-     * OpenIdService constructor.
-     */
     public function __construct(
-        RegistrationService $registrationService,
-        LoginService $loginService,
-        HttpClient $httpClient,
-        GroupSyncService $groupService
+        protected RegistrationService $registrationService,
+        protected LoginService $loginService,
+        protected HttpClient $httpClient,
+        protected GroupSyncService $groupService
     ) {
-        $this->registrationService = $registrationService;
-        $this->loginService = $loginService;
-        $this->httpClient = $httpClient;
-        $this->groupService = $groupService;
     }
 
     /**
@@ -226,6 +216,16 @@ class OidcService
             $settings->keys,
         );
 
+        $returnClaims = Theme::dispatch(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $idToken->getAllClaims(), [
+            'access_token' => $accessToken->getToken(),
+            'expires_in' => $accessToken->getExpires(),
+            'refresh_token' => $accessToken->getRefreshToken(),
+        ]);
+
+        if (!is_null($returnClaims)) {
+            $idToken->replaceClaims($returnClaims);
+        }
+
         if ($this->config()['dump_user_details']) {
             throw new JsonDebugException($idToken->getAllClaims());
         }
diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php
index 91f4fcd67..aacef80cf 100644
--- a/app/Theming/ThemeEvents.php
+++ b/app/Theming/ThemeEvents.php
@@ -70,6 +70,19 @@ class ThemeEvents
      */
     const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
 
+    /**
+     * OIDC ID token pre-validate event.
+     * Runs just before BookStack validates the user ID token data upon login.
+     * Provides the existing found set of claims for the user as a key-value array,
+     * along with an array of the proceeding access token data provided by the identity platform.
+     * If the listener returns a non-null value, that will replace the existing ID token claim data.
+     *
+     * @param array $idTokenData
+     * @param array $accessTokenData
+     * @returns array|null
+     */
+    const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate';
+
     /**
      * Page include parse event.
      * Runs when a page include tag is being parsed, typically when page content is being processed for viewing.
diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php
index 35acb7752..41727e7b7 100644
--- a/tests/Auth/OidcTest.php
+++ b/tests/Auth/OidcTest.php
@@ -5,6 +5,8 @@ namespace Tests\Auth;
 use BookStack\Actions\ActivityType;
 use BookStack\Auth\Role;
 use BookStack\Auth\User;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
 use GuzzleHttp\Psr7\Request;
 use GuzzleHttp\Psr7\Response;
 use Illuminate\Testing\TestResponse;
@@ -397,7 +399,6 @@ class OidcTest extends TestCase
         config()->set([
             'oidc.external_id_claim' => 'super_awesome_id',
         ]);
-        $roleA = Role::factory()->create(['display_name' => 'Wizards']);
 
         $resp = $this->runLogin([
             'email'            => 'benny@example.com',
@@ -464,6 +465,60 @@ class OidcTest extends TestCase
         $this->assertTrue($user->hasRole($roleA->id));
     }
 
+    public function test_oidc_id_token_pre_validate_theme_event_without_return()
+    {
+        $args = [];
+        $callback = function (...$eventArgs) use (&$args) {
+            $args = $eventArgs;
+        };
+        Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
+
+        $resp = $this->runLogin([
+            'email' => 'benny@example.com',
+            'sub'   => 'benny1010101',
+            'name'  => 'Benny',
+        ]);
+        $resp->assertRedirect('/');
+
+        $this->assertDatabaseHas('users', [
+            'external_auth_id' => 'benny1010101',
+        ]);
+
+        $this->assertArrayHasKey('iss', $args[0]);
+        $this->assertArrayHasKey('sub', $args[0]);
+        $this->assertEquals('Benny', $args[0]['name']);
+        $this->assertEquals('benny1010101', $args[0]['sub']);
+
+        $this->assertArrayHasKey('access_token', $args[1]);
+        $this->assertArrayHasKey('expires_in', $args[1]);
+        $this->assertArrayHasKey('refresh_token', $args[1]);
+    }
+
+    public function test_oidc_id_token_pre_validate_theme_event_with_return()
+    {
+        $callback = function (...$eventArgs) {
+            return array_merge($eventArgs[0], [
+                'email' => 'lenny@example.com',
+                'sub' => 'lenny1010101',
+                'name' => 'Lenny',
+            ]);
+        };
+        Theme::listen(ThemeEvents::OIDC_ID_TOKEN_PRE_VALIDATE, $callback);
+
+        $resp = $this->runLogin([
+            'email' => 'benny@example.com',
+            'sub'   => 'benny1010101',
+            'name'  => 'Benny',
+        ]);
+        $resp->assertRedirect('/');
+
+        $this->assertDatabaseHas('users', [
+            'email' => 'lenny@example.com',
+            'external_auth_id' => 'lenny1010101',
+            'name' => 'Lenny',
+        ]);
+    }
+
     protected function withAutodiscovery()
     {
         config()->set([
diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php
index 03ae7b307..bc8163056 100644
--- a/tests/ThemeTest.php
+++ b/tests/ThemeTest.php
@@ -23,8 +23,8 @@ use League\CommonMark\Environment\Environment;
 
 class ThemeTest extends TestCase
 {
-    protected $themeFolderName;
-    protected $themeFolderPath;
+    protected string $themeFolderName;
+    protected string $themeFolderPath;
 
     public function test_translation_text_can_be_overridden_via_theme()
     {