3 namespace Drupal\basic_auth\Authentication\Provider;
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Authentication\AuthenticationProviderInterface;
7 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
8 use Drupal\Core\Config\ConfigFactoryInterface;
9 use Drupal\Core\Entity\EntityManagerInterface;
10 use Drupal\Core\Flood\FloodInterface;
11 use Drupal\user\UserAuthInterface;
12 use Symfony\Component\HttpFoundation\Request;
13 use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
16 * HTTP Basic authentication provider.
18 class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
23 * @var \Drupal\Core\Config\ConfigFactoryInterface
25 protected $configFactory;
28 * The user auth service.
30 * @var \Drupal\user\UserAuthInterface
37 * @var \Drupal\Core\Flood\FloodInterface
44 * @var \Drupal\Core\Entity\EntityManagerInterface
46 protected $entityManager;
49 * Constructs a HTTP basic authentication provider object.
51 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
53 * @param \Drupal\user\UserAuthInterface $user_auth
54 * The user authentication service.
55 * @param \Drupal\Core\Flood\FloodInterface $flood
57 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
58 * The entity manager service.
60 public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityManagerInterface $entity_manager) {
61 $this->configFactory = $config_factory;
62 $this->userAuth = $user_auth;
63 $this->flood = $flood;
64 $this->entityManager = $entity_manager;
70 public function applies(Request $request) {
71 $username = $request->headers->get('PHP_AUTH_USER');
72 $password = $request->headers->get('PHP_AUTH_PW');
73 return isset($username) && isset($password);
79 public function authenticate(Request $request) {
80 $flood_config = $this->configFactory->get('user.flood');
81 $username = $request->headers->get('PHP_AUTH_USER');
82 $password = $request->headers->get('PHP_AUTH_PW');
83 // Flood protection: this is very similar to the user login form code.
84 // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
85 // Do not allow any login from the current user's IP if the limit has been
86 // reached. Default is 50 failed attempts allowed in one hour. This is
87 // independent of the per-user limit to catch attempts from one IP to log
88 // in to many different user accounts. We have a reasonably high limit
89 // since there may be only one apparent IP for all users at an institution.
90 if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
91 $accounts = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]);
92 $account = reset($accounts);
94 if ($flood_config->get('uid_only')) {
95 // Register flood events based on the uid only, so they apply for any
96 // IP address. This is the most secure option.
97 $identifier = $account->id();
100 // The default identifier is a combination of uid and IP address. This
101 // is less secure but more resistant to denial-of-service attacks that
102 // could lock out all users with public user names.
103 $identifier = $account->id() . '-' . $request->getClientIP();
105 // Don't allow login if the limit for this user has been reached.
106 // Default is to allow 5 failed attempts every 6 hours.
107 if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
108 $uid = $this->userAuth->authenticate($username, $password);
110 $this->flood->clear('basic_auth.failed_login_user', $identifier);
111 return $this->entityManager->getStorage('user')->load($uid);
114 // Register a per-user failed login event.
115 $this->flood->register('basic_auth.failed_login_user', $flood_config->get('user_window'), $identifier);
120 // Always register an IP-based failed login event.
121 $this->flood->register('basic_auth.failed_login_ip', $flood_config->get('ip_window'));
128 public function challengeException(Request $request, \Exception $previous) {
129 $site_name = $this->configFactory->get('system.site')->get('name');
130 $challenge = SafeMarkup::format('Basic realm="@realm"', [
131 '@realm' => !empty($site_name) ? $site_name : 'Access restricted',
133 return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);