.../src/Authentication/Provider/BasicAuth.php | 31 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php index c72e3f0..01034f4 100644 --- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php @@ -5,12 +5,13 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Authentication\AuthenticationProviderInterface; use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Flood\FloodInterface; +use Drupal\Core\Http\Exception\CacheableUnauthorizedHttpException; use Drupal\user\UserAuthInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; /** * HTTP Basic authentication provider. @@ -126,11 +127,35 @@ public function authenticate(Request $request) { * {@inheritdoc} */ public function challengeException(Request $request, \Exception $previous) { - $site_name = $this->configFactory->get('system.site')->get('name'); + $site_config = $this->configFactory->get('system.site'); + $site_name = $site_config->get('name'); $challenge = SafeMarkup::format('Basic realm="@realm"', [ '@realm' => !empty($site_name) ? $site_name : 'Access restricted', ]); - return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous); + + // A 403 is converted to a 401 here, but it doesn't matter what the + // cacheability was of the 403 exception: what matters here is that + // authentication credentials are missing, i.e. that this request was made + // as the anonymous user. + // Therefore, all we must do, is make this response: + // 1. vary by whether the current user has the 'anonymous' role or not. This + // works fine because: + // - Thanks to \Drupal\basic_auth\PageCache\DisallowBasicAuthRequests, + // Page Cache never caches a response whose request has Basic Auth + // credentials. + // - Dynamic Page Cache will cache a different result for when the + // request is unauthenticated (this 401) versus authenticated (some + // other response) + // 2. have the 'config:user.role.anonymous' cache tag, because the only + // reason this 401 would no longer be a 401 is if permissions for the + // 'anonymous' role change, causing that cache tag to be invalidated. + // @see \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onExceptionSendChallenge() + // @see \Drupal\Core\EventSubscriber\ClientErrorResponseSubscriber() + // @see \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onAllResponds() + $cacheability = CacheableMetadata::createFromObject($site_config) + ->addCacheTags(['config:user.role.anonymous']) + ->addCacheContexts(['user.roles:anonymous']); + return new CacheableUnauthorizedHttpException($cacheability, (string) $challenge, 'No authentication credentials provided.', $previous); } }