--- /dev/null
+<?php
+
+namespace Drupal\basic_auth\Authentication\Provider;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Flood\FloodInterface;
+use Drupal\user\UserAuthInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
+
+/**
+ * HTTP Basic authentication provider.
+ */
+class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
+
+ /**
+ * The config factory.
+ *
+ * @var \Drupal\Core\Config\ConfigFactoryInterface
+ */
+ protected $configFactory;
+
+ /**
+ * The user auth service.
+ *
+ * @var \Drupal\user\UserAuthInterface
+ */
+ protected $userAuth;
+
+ /**
+ * The flood service.
+ *
+ * @var \Drupal\Core\Flood\FloodInterface
+ */
+ protected $flood;
+
+ /**
+ * The entity manager.
+ *
+ * @var \Drupal\Core\Entity\EntityManagerInterface
+ */
+ protected $entityManager;
+
+ /**
+ * Constructs a HTTP basic authentication provider object.
+ *
+ * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+ * The config factory.
+ * @param \Drupal\user\UserAuthInterface $user_auth
+ * The user authentication service.
+ * @param \Drupal\Core\Flood\FloodInterface $flood
+ * The flood service.
+ * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+ * The entity manager service.
+ */
+ public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityManagerInterface $entity_manager) {
+ $this->configFactory = $config_factory;
+ $this->userAuth = $user_auth;
+ $this->flood = $flood;
+ $this->entityManager = $entity_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'))) {
+ $accounts = $this->entityManager->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 = $this->userAuth->authenticate($username, $password);
+ if ($uid) {
+ $this->flood->clear('basic_auth.failed_login_user', $identifier);
+ return $this->entityManager->getStorage('user')->load($uid);
+ }
+ 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 [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function challengeException(Request $request, \Exception $previous) {
+ $site_name = $this->configFactory->get('system.site')->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);
+ }
+
+}