diff --git a/src/Authorization/AbstractAuthorizationService.php b/src/Authorization/AbstractAuthorizationService.php
index fc69b1a6cf8120c5e1c7304bc1b4e0843bc158e5..28cefd43111b5921df06271befba80bd4a47f482 100644
--- a/src/Authorization/AbstractAuthorizationService.php
+++ b/src/Authorization/AbstractAuthorizationService.php
@@ -16,19 +16,32 @@ abstract class AbstractAuthorizationService
     /** @var AuthorizationExpressionChecker */
     private $userAuthorizationChecker;
 
-    /** @var AuthorizationUser|null */
+    /** @var AuthorizationUser */
     private $currentAuthorizationUser;
 
-    public function __construct(UserSessionInterface $userSession, AuthorizationDataProviderProvider $authorizationDataProviderProvider)
+    private $config;
+
+    /**
+     * @required
+     */
+    public function _injectServices(UserSessionInterface $userSession, AuthorizationDataMuxer $mux)
     {
-        $muxer = new AuthorizationDataMuxer($authorizationDataProviderProvider->getAuthorizationDataProviders());
-        $this->userAuthorizationChecker = new AuthorizationExpressionChecker($muxer);
+        $this->userAuthorizationChecker = new AuthorizationExpressionChecker($mux);
         $this->currentAuthorizationUser = new AuthorizationUser($userSession->getUserIdentifier(), $this->userAuthorizationChecker);
+        $this->updateConfig();
     }
 
     public function setConfig(array $config)
     {
-        $this->userAuthorizationChecker->setConfig($config);
+        $this->config = $config;
+        $this->updateConfig();
+    }
+
+    private function updateConfig()
+    {
+        if ($this->userAuthorizationChecker !== null && $this->config !== null) {
+            $this->userAuthorizationChecker->setConfig($this->config);
+        }
     }
 
     /**
diff --git a/src/Authorization/AuthorizationDataMuxer.php b/src/Authorization/AuthorizationDataMuxer.php
index 424d35dcca555d147dbdb560216ca83bf852c93b..f3e0673d17a8546d78808017b3bbf81eec135030 100644
--- a/src/Authorization/AuthorizationDataMuxer.php
+++ b/src/Authorization/AuthorizationDataMuxer.php
@@ -4,83 +4,143 @@ declare(strict_types=1);
 
 namespace Dbp\Relay\CoreBundle\Authorization;
 
+use Dbp\Relay\CoreBundle\Authorization\Event\GetAttributeEvent;
+use Dbp\Relay\CoreBundle\Authorization\Event\GetAvailableAttributesEvent;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+/**
+ * @internal
+ */
 class AuthorizationDataMuxer
 {
-    /** @var iterable */
+    /** @var iterable<AuthorizationDataProviderInterface> */
     private $authorizationDataProviders;
 
-    /** @var array */
-    private $attributes;
+    /** @var array<string, array> */
+    private $providerCache;
+
+    /** @var array<string, string[]> */
+    private $availableCache;
+
+    /** @var EventDispatcherInterface */
+    private $eventDispatcher;
+
+    /** @var string[] */
+    private $attributeStack;
 
     /**
-     * @param iterable<AuthorizationDataProviderInterface> $authorizationDataProviders
+     * @var ?string[]
      */
-    public function __construct(iterable $authorizationDataProviders)
+    private $availableCacheAll;
+
+    public function __construct(AuthorizationDataProviderProvider $authorizationDataProviderProvider, EventDispatcherInterface $eventDispatcher)
     {
-        $this->authorizationDataProviders = $authorizationDataProviders;
-        $this->attributes = [];
+        $this->authorizationDataProviders = $authorizationDataProviderProvider->getAuthorizationDataProviders();
+        $this->eventDispatcher = $eventDispatcher;
+        $this->providerCache = [];
+        $this->availableCache = [];
+        $this->availableCacheAll = null;
+        $this->attributeStack = [];
     }
 
-    private function loadUserAttributesFromAuthorizationProvider(?string $userIdentifier, AuthorizationDataProviderInterface $authorizationDataProvider): void
+    /**
+     * Returns an array of available attributes.
+     *
+     * @return string[]
+     */
+    public function getAvailableAttributes(): array
     {
-        $userAttributes = $authorizationDataProvider->getUserAttributes($userIdentifier);
-
-        foreach ($authorizationDataProvider->getAvailableAttributes() as $availableAttribute) {
-            if (array_key_exists($availableAttribute, $userAttributes)) {
-                $this->attributes[$availableAttribute] = $userAttributes[$availableAttribute];
+        if ($this->availableCacheAll === null) {
+            $res = [];
+            foreach ($this->authorizationDataProviders as $authorizationDataProvider) {
+                $availableAttributes = $this->getProviderAvailableAttributes($authorizationDataProvider);
+                $res = array_merge($res, $availableAttributes);
             }
+
+            $event = new GetAvailableAttributesEvent($res);
+            $this->eventDispatcher->dispatch($event);
+            $this->availableCacheAll = $event->getAttributes();
         }
+
+        return $this->availableCacheAll;
     }
 
     /**
-     * Returns an array of available attributes.
+     * Returns a cached list for available attributes for the provider.
      *
      * @return string[]
      */
-    public function getAvailableAttributes(): array
+    private function getProviderAvailableAttributes(AuthorizationDataProviderInterface $prov): array
     {
-        $res = [];
-        foreach ($this->authorizationDataProviders as $authorizationDataProvider) {
-            $availableAttributes = $authorizationDataProvider->getAvailableAttributes();
-            $res = array_merge($res, $availableAttributes);
+        // Caches getAvailableAttributes for each provider
+        $provKey = get_class($prov);
+        if (!array_key_exists($provKey, $this->availableCache)) {
+            $this->availableCache[$provKey] = $prov->getAvailableAttributes();
         }
 
-        return $res;
+        return $this->availableCache[$provKey];
     }
 
     /**
-     * @param mixed|null $defaultValue
+     * Returns a cached map of available user attributes.
      *
-     * @return mixed|null
-     *
-     * @throws AuthorizationException
+     * @return array<string, mixed>
      */
-    public function getAttribute(?string $userIdentifier, string $attributeName, $defaultValue = null)
+    private function getProviderUserAttributes(AuthorizationDataProviderInterface $prov, ?string $userIdentifier): array
     {
-        if (array_key_exists($attributeName, $this->attributes) === false) {
-            $this->loadAttribute($userIdentifier, $attributeName);
+        // We cache the attributes for each provider, but only for the last userIdentifier
+        $provKey = get_class($prov);
+        if (!array_key_exists($provKey, $this->providerCache) || $this->providerCache[$provKey][0] !== $userIdentifier) {
+            $this->providerCache[$provKey] = [$userIdentifier, $prov->getUserAttributes($userIdentifier)];
         }
+        $res = $this->providerCache[$provKey];
+        assert($res[0] === $userIdentifier);
 
-        return $this->attributes[$attributeName] ?? $defaultValue;
+        return $res[1];
     }
 
     /**
+     * @param mixed $defaultValue
+     *
+     * @return mixed
+     *
      * @throws AuthorizationException
      */
-    private function loadAttribute(?string $userIdentifier, string $attributeName): void
+    public function getAttribute(?string $userIdentifier, string $attributeName, $defaultValue = null)
     {
+        if (!in_array($attributeName, $this->getAvailableAttributes(), true)) {
+            throw new AuthorizationException(sprintf('attribute \'%s\' undefined', $attributeName), AuthorizationException::ATTRIBUTE_UNDEFINED);
+        }
+
         $wasFound = false;
+        $value = null;
         foreach ($this->authorizationDataProviders as $authorizationDataProvider) {
-            $availableAttributes = $authorizationDataProvider->getAvailableAttributes();
-            if (in_array($attributeName, $availableAttributes, true)) {
-                $this->loadUserAttributesFromAuthorizationProvider($userIdentifier, $authorizationDataProvider);
-                $wasFound = true;
-                break;
+            $availableAttributes = $this->getProviderAvailableAttributes($authorizationDataProvider);
+            if (!in_array($attributeName, $availableAttributes, true)) {
+                continue;
             }
+            $userAttributes = $this->getProviderUserAttributes($authorizationDataProvider, $userIdentifier);
+            if (!array_key_exists($attributeName, $userAttributes)) {
+                continue;
+            }
+            $value = $userAttributes[$attributeName];
+            $wasFound = true;
+            break;
         }
 
-        if ($wasFound === false) {
-            throw new AuthorizationException(sprintf('custom attribute \'%s\' undefined', $attributeName), AuthorizationException::ATTRIBUTE_UNDEFINED);
+        $event = new GetAttributeEvent($this, $attributeName, $userIdentifier);
+
+        if ($wasFound) {
+            $event->setValue($value);
         }
+
+        // Avoid endless recursions by only emitting an event for each attribtue only once
+        if (!in_array($attributeName, $this->attributeStack, true)) {
+            array_push($this->attributeStack, $attributeName);
+            $this->eventDispatcher->dispatch($event);
+            array_pop($this->attributeStack);
+        }
+
+        return $event->getValue($defaultValue);
     }
 }
diff --git a/src/Authorization/AuthorizationExpressionChecker.php b/src/Authorization/AuthorizationExpressionChecker.php
index 2363d9ef40e4ec061401222ff6b00c89f70ed256..c903ff85c947f5bd1bd8284a1b201c806c668ff4 100644
--- a/src/Authorization/AuthorizationExpressionChecker.php
+++ b/src/Authorization/AuthorizationExpressionChecker.php
@@ -6,6 +6,9 @@ namespace Dbp\Relay\CoreBundle\Authorization;
 
 use Dbp\Relay\CoreBundle\Authorization\ExpressionLanguage\ExpressionLanguage;
 
+/**
+ * @internal
+ */
 class AuthorizationExpressionChecker
 {
     public const RIGHTS_CONFIG_ATTRIBUTE = 'rights';
diff --git a/src/Authorization/DebugCommand.php b/src/Authorization/DebugCommand.php
index 619d0ac2c39e1b1c56d3566d45925e4c796b90bc..174a98ec8ae380cb258123882d8dcce039369dd4 100644
--- a/src/Authorization/DebugCommand.php
+++ b/src/Authorization/DebugCommand.php
@@ -18,14 +18,14 @@ class DebugCommand extends Command implements LoggerAwareInterface
     protected static $defaultName = 'dbp:relay:core:auth-debug';
 
     /**
-     * @var AuthorizationDataProviderProvider
+     * @var AuthorizationDataMuxer
      */
-    private $provider;
+    private $mux;
 
-    public function __construct(AuthorizationDataProviderProvider $provider)
+    public function __construct(AuthorizationDataMuxer $mux)
     {
         parent::__construct();
-        $this->provider = $provider;
+        $this->mux = $mux;
     }
 
     protected function configure()
@@ -38,15 +38,12 @@ class DebugCommand extends Command implements LoggerAwareInterface
     {
         $username = $input->getArgument('username');
 
-        // Fetch all attributes first (to get potential log spam first)
-        $providers = $this->provider->getAuthorizationDataProviders();
-        $mux = new AuthorizationDataMuxer($providers);
-        $attrs = $mux->getAvailableAttributes();
+        $attrs = $this->mux->getAvailableAttributes();
         $all = [];
         $default = new \stdClass();
         sort($attrs, SORT_STRING | SORT_FLAG_CASE);
         foreach ($attrs as $attr) {
-            $all[$attr] = $mux->getAttribute($username, $attr, $default);
+            $all[$attr] = $this->mux->getAttribute($username, $attr, $default);
         }
 
         // Now print them out
diff --git a/src/Authorization/Event/GetAttributeEvent.php b/src/Authorization/Event/GetAttributeEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..d825d5236a2755b2558b1b886a58c6bdf64687f0
--- /dev/null
+++ b/src/Authorization/Event/GetAttributeEvent.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dbp\Relay\CoreBundle\Authorization\Event;
+
+use Dbp\Relay\CoreBundle\Authorization\AuthorizationDataMuxer;
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * This hook can be used to change the value of attributes at the time they are requested.
+ *
+ * Can be used to change existing attribute values, and introduce new attributes.
+ * In case of new attributes you have to make sure to also handle GetAvailableAttributesEvent
+ * and register your new attribute there.
+ */
+class GetAttributeEvent extends Event
+{
+    /**
+     * @var AuthorizationDataMuxer
+     */
+    private $mux;
+
+    /**
+     * @var ?string
+     */
+    private $userIdentifier;
+
+    /**
+     * @var string
+     */
+    private $name;
+
+    /**
+     * @var mixed
+     */
+    private $value;
+
+    /**
+     * @var bool
+     */
+    private $hasValue;
+
+    public function __construct(AuthorizationDataMuxer $mux, string $name, ?string $userIdentifier)
+    {
+        $this->mux = $mux;
+        $this->userIdentifier = $userIdentifier;
+        $this->name = $name;
+        $this->hasValue = false;
+    }
+
+    /**
+     * @param mixed|null $defaultValue
+     *
+     * @return mixed|null
+     */
+    public function getAttribute(string $attributeName, $defaultValue = null)
+    {
+        return $this->mux->getAttribute($this->userIdentifier, $attributeName, $defaultValue);
+    }
+
+    public function getAttributeName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param mixed $value
+     */
+    public function setValue($value): void
+    {
+        $this->value = $value;
+        $this->hasValue = true;
+    }
+
+    /**
+     * @param mixed $default
+     *
+     * @return mixed
+     */
+    public function getValue($default = null)
+    {
+        if (!$this->hasValue) {
+            return $default;
+        }
+
+        return $this->value;
+    }
+}
diff --git a/src/Authorization/Event/GetAvailableAttributesEvent.php b/src/Authorization/Event/GetAvailableAttributesEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..8cefaa60e3e6286dbbe9deae34ee004805508491
--- /dev/null
+++ b/src/Authorization/Event/GetAvailableAttributesEvent.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Dbp\Relay\CoreBundle\Authorization\Event;
+
+use Symfony\Contracts\EventDispatcher\Event;
+
+/**
+ * This hook can be used to change the set of available attributes.
+ *
+ * You can extend the set, or remove attributes.
+ */
+class GetAvailableAttributesEvent extends Event
+{
+    /**
+     * @var string[]
+     */
+    private $attributes;
+
+    /**
+     * @param string[] $attributes
+     */
+    public function __construct(array $attributes)
+    {
+        $this->attributes = $attributes;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getAttributes(): array
+    {
+        return $this->attributes;
+    }
+
+    /**
+     * @param string[] $attributes
+     */
+    public function setAttributes(array $attributes): void
+    {
+        $this->attributes = $attributes;
+    }
+}
diff --git a/src/LocalData/AbstractLocalDataPostEventSubscriber.php b/src/LocalData/AbstractLocalDataPostEventSubscriber.php
index 9000426de303c2fc084814c9a941f43723b522cc..6d6115c591104235c976e0fa55f2995369af2313 100644
--- a/src/LocalData/AbstractLocalDataPostEventSubscriber.php
+++ b/src/LocalData/AbstractLocalDataPostEventSubscriber.php
@@ -4,9 +4,7 @@ declare(strict_types=1);
 
 namespace Dbp\Relay\CoreBundle\LocalData;
 
-use Dbp\Relay\CoreBundle\API\UserSessionInterface;
 use Dbp\Relay\CoreBundle\Authorization\AbstractAuthorizationService;
-use Dbp\Relay\CoreBundle\Authorization\AuthorizationDataProviderProvider;
 use Symfony\Component\Config\Definition\Builder\TreeBuilder;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
@@ -20,10 +18,8 @@ abstract class AbstractLocalDataPostEventSubscriber extends AbstractAuthorizatio
     /** @var string[] */
     private $attributeMapping;
 
-    public function __construct(UserSessionInterface $userSession, AuthorizationDataProviderProvider $authorizationDataProviderProvider)
+    public function __construct()
     {
-        parent::__construct($userSession, $authorizationDataProviderProvider);
-
         $this->attributeMapping = [];
     }
 
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index 192524e7278932b458ac209336641e305b616c2f..7f13c44687bc6993ee12a9b9fe79c3a3724faead 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -93,6 +93,10 @@ services:
     autoconfigure: true
     arguments: [ !tagged auth.authorization_data_provider ]
 
+  Dbp\Relay\CoreBundle\Authorization\AuthorizationDataMuxer:
+    autowire: true
+    autoconfigure: true
+
   Dbp\Relay\CoreBundle\Helpers\Locale:
     autowire: true
     autoconfigure: true