class BasicAuth

Same name and namespace in other branches
  1. 9 core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php \Drupal\basic_auth\Authentication\Provider\BasicAuth
  2. 8.9.x core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php \Drupal\basic_auth\Authentication\Provider\BasicAuth
  3. 11.x core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php \Drupal\basic_auth\Authentication\Provider\BasicAuth

HTTP Basic authentication provider.

Hierarchy

Expanded class hierarchy of BasicAuth

1 string reference to 'BasicAuth'
basic_auth.services.yml in core/modules/basic_auth/basic_auth.services.yml
core/modules/basic_auth/basic_auth.services.yml
1 service uses BasicAuth
basic_auth.authentication.basic_auth in core/modules/basic_auth/basic_auth.services.yml
Drupal\basic_auth\Authentication\Provider\BasicAuth

File

core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php, line 21

Namespace

Drupal\basic_auth\Authentication\Provider
View source
class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
  
  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;
  
  /**
   * The user auth service.
   *
   * @var \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface
   */
  protected $userAuth;
  
  /**
   * The flood service.
   *
   * @var \Drupal\Core\Flood\FloodInterface
   */
  protected $flood;
  
  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;
  
  /**
   * Constructs a HTTP basic authentication provider object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\user\UserAuthInterface|\Drupal\user\UserAuthenticationInterface $user_auth
   *   The user authentication service.
   * @param \Drupal\Core\Flood\FloodInterface $flood
   *   The flood service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   */
  public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface|UserAuthenticationInterface $user_auth, FloodInterface $flood, EntityTypeManagerInterface $entity_type_manager) {
    $this->configFactory = $config_factory;
    if (!$user_auth instanceof UserAuthenticationInterface) {
      @trigger_error('The $user_auth parameter implementing UserAuthInterface is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Implement UserAuthenticationInterface instead. See https://www.drupal.org/node/3411040');
    }
    $this->userAuth = $user_auth;
    $this->flood = $flood;
    $this->entityTypeManager = $entity_type_manager;
  }
  
  /**
   * {@inheritdoc}
   */
  public function applies(Request $request) {
    $username = $request->headers
      ->get('PHP_AUTH_USER');
    $password = $request->headers
      ->get('PHP_AUTH_PW');
    return isset($username) && isset($password);
  }
  
  /**
   * {@inheritdoc}
   */
  public function authenticate(Request $request) {
    $flood_config = $this->configFactory
      ->get('user.flood');
    $username = $request->headers
      ->get('PHP_AUTH_USER');
    $password = $request->headers
      ->get('PHP_AUTH_PW');
    // Flood protection: this is very similar to the user login form code.
    // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
    // Do not allow any login from the current user's IP if the limit has been
    // reached. Default is 50 failed attempts allowed in one hour. This is
    // independent of the per-user limit to catch attempts from one IP to log
    // in to many different user accounts.  We have a reasonably high limit
    // since there may be only one apparent IP for all users at an institution.
    if ($this->flood
      ->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
      $account = FALSE;
      if ($this->userAuth instanceof UserAuthenticationInterface) {
        $lookup = $this->userAuth
          ->lookupAccount($username);
        if ($lookup && !$lookup->isBlocked()) {
          $account = $lookup;
        }
      }
      else {
        $accounts = $this->entityTypeManager
          ->getStorage('user')
          ->loadByProperties([
          'name' => $username,
          'status' => 1,
        ]);
        $account = reset($accounts);
      }
      if ($account) {
        if ($flood_config->get('uid_only')) {
          // Register flood events based on the uid only, so they apply for any
          // IP address. This is the most secure option.
          $identifier = $account->id();
        }
        else {
          // The default identifier is a combination of uid and IP address. This
          // is less secure but more resistant to denial-of-service attacks that
          // could lock out all users with public user names.
          $identifier = $account->id() . '-' . $request->getClientIP();
        }
        // Don't allow login if the limit for this user has been reached.
        // Default is to allow 5 failed attempts every 6 hours.
        if ($this->flood
          ->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
          $uid = FALSE;
          if ($this->userAuth instanceof UserAuthenticationInterface) {
            $uid = $this->userAuth
              ->authenticateAccount($account, $password) ? $account->id() : FALSE;
          }
          else {
            $uid = $this->userAuth
              ->authenticate($username, $password);
          }
          if ($uid) {
            $this->flood
              ->clear('basic_auth.failed_login_user', $identifier);
            return $account;
          }
          else {
            // Register a per-user failed login event.
            $this->flood
              ->register('basic_auth.failed_login_user', $flood_config->get('user_window'), $identifier);
          }
        }
      }
    }
    // Always register an IP-based failed login event.
    $this->flood
      ->register('basic_auth.failed_login_ip', $flood_config->get('ip_window'));
    return NULL;
  }
  
  /**
   * {@inheritdoc}
   */
  public function challengeException(Request $request, \Exception $previous) {
    $site_config = $this->configFactory
      ->get('system.site');
    $site_name = $site_config->get('name');
    $challenge = new FormattableMarkup('Basic realm="@realm"', [
      '@realm' => !empty($site_name) ? $site_name : 'Access restricted',
    ]);
    // 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. this request was made
    // as an anonymous user.
    // Therefore, the following actions will be taken:
    // 1. Verify 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 the 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 $request->isMethodCacheable() ? new CacheableUnauthorizedHttpException($cacheability, (string) $challenge, 'No authentication credentials provided.', $previous) : new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
  }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
BasicAuth::$configFactory protected property The config factory.
BasicAuth::$entityTypeManager protected property The entity type manager service.
BasicAuth::$flood protected property The flood service.
BasicAuth::$userAuth protected property The user auth service.
BasicAuth::applies public function Checks whether suitable authentication credentials are on the request. Overrides AuthenticationProviderInterface::applies
BasicAuth::authenticate public function Authenticates the user. Overrides AuthenticationProviderInterface::authenticate
BasicAuth::challengeException public function Constructs an exception which is used to generate the challenge. Overrides AuthenticationProviderChallengeInterface::challengeException
BasicAuth::__construct public function Constructs a HTTP basic authentication provider object.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.