class UserAuthenticationController

Same name in other branches
  1. 8.9.x core/modules/user/src/Controller/UserAuthenticationController.php \Drupal\user\Controller\UserAuthenticationController
  2. 10 core/modules/user/src/Controller/UserAuthenticationController.php \Drupal\user\Controller\UserAuthenticationController
  3. 11.x core/modules/user/src/Controller/UserAuthenticationController.php \Drupal\user\Controller\UserAuthenticationController

Provides controllers for login, login status and logout via HTTP requests.

Hierarchy

Expanded class hierarchy of UserAuthenticationController

2 files declare their use of UserAuthenticationController
UserAuthenticationControllerTest.php in core/modules/user/tests/src/Kernel/Controller/UserAuthenticationControllerTest.php
UserLoginHttpTest.php in core/modules/user/tests/src/Functional/UserLoginHttpTest.php

File

core/modules/user/src/Controller/UserAuthenticationController.php, line 25

Namespace

Drupal\user\Controller
View source
class UserAuthenticationController extends ControllerBase implements ContainerInjectionInterface {
    
    /**
     * String sent in responses, to describe the user as being logged in.
     *
     * @var string
     */
    const LOGGED_IN = 1;
    
    /**
     * String sent in responses, to describe the user as being logged out.
     *
     * @var string
     */
    const LOGGED_OUT = 0;
    
    /**
     * The user flood control service.
     *
     * @var \Drupal\user\UserFloodControl
     */
    protected $userFloodControl;
    
    /**
     * The user storage.
     *
     * @var \Drupal\user\UserStorageInterface
     */
    protected $userStorage;
    
    /**
     * The CSRF token generator.
     *
     * @var \Drupal\Core\Access\CsrfTokenGenerator
     */
    protected $csrfToken;
    
    /**
     * The user authentication.
     *
     * @var \Drupal\user\UserAuthInterface
     */
    protected $userAuth;
    
    /**
     * The route provider.
     *
     * @var \Drupal\Core\Routing\RouteProviderInterface
     */
    protected $routeProvider;
    
    /**
     * The serializer.
     *
     * @var \Symfony\Component\Serializer\Serializer
     */
    protected $serializer;
    
    /**
     * The available serialization formats.
     *
     * @var array
     */
    protected $serializerFormats = [];
    
    /**
     * A logger instance.
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;
    
    /**
     * Constructs a new UserAuthenticationController object.
     *
     * @param \Drupal\user\UserFloodControlInterface $user_flood_control
     *   The user flood control service.
     * @param \Drupal\user\UserStorageInterface $user_storage
     *   The user storage.
     * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
     *   The CSRF token generator.
     * @param \Drupal\user\UserAuthInterface $user_auth
     *   The user authentication.
     * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
     *   The route provider.
     * @param \Symfony\Component\Serializer\Serializer $serializer
     *   The serializer.
     * @param array $serializer_formats
     *   The available serialization formats.
     * @param \Psr\Log\LoggerInterface $logger
     *   A logger instance.
     */
    public function __construct($user_flood_control, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
        if (!$user_flood_control instanceof UserFloodControlInterface) {
            @trigger_error('Passing the flood service to ' . __METHOD__ . ' is deprecated in drupal:9.1.0 and is replaced by user.flood_control in drupal:10.0.0. See https://www.drupal.org/node/3067148', E_USER_DEPRECATED);
            $user_flood_control = \Drupal::service('user.flood_control');
        }
        $this->userFloodControl = $user_flood_control;
        $this->userStorage = $user_storage;
        $this->csrfToken = $csrf_token;
        $this->userAuth = $user_auth;
        $this->serializer = $serializer;
        $this->serializerFormats = $serializer_formats;
        $this->routeProvider = $route_provider;
        $this->logger = $logger;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        if ($container->hasParameter('serializer.formats') && $container->has('serializer')) {
            $serializer = $container->get('serializer');
            $formats = $container->getParameter('serializer.formats');
        }
        else {
            $formats = [
                'json',
            ];
            $encoders = [
                new JsonEncoder(),
            ];
            $serializer = new Serializer([], $encoders);
        }
        return new static($container->get('user.flood_control'), $container->get('entity_type.manager')
            ->getStorage('user'), $container->get('csrf_token'), $container->get('user.auth'), $container->get('router.route_provider'), $serializer, $formats, $container->get('logger.factory')
            ->get('user'));
    }
    
    /**
     * Logs in a user.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The request.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *   A response which contains the ID and CSRF token.
     */
    public function login(Request $request) {
        $format = $this->getRequestFormat($request);
        $content = $request->getContent();
        $credentials = $this->serializer
            ->decode($content, $format);
        if (!isset($credentials['name']) && !isset($credentials['pass'])) {
            throw new BadRequestHttpException('Missing credentials.');
        }
        if (!isset($credentials['name'])) {
            throw new BadRequestHttpException('Missing credentials.name.');
        }
        if (!isset($credentials['pass'])) {
            throw new BadRequestHttpException('Missing credentials.pass.');
        }
        $this->floodControl($request, $credentials['name']);
        if ($this->userIsBlocked($credentials['name'])) {
            throw new BadRequestHttpException('The user has not been activated or is blocked.');
        }
        if ($uid = $this->userAuth
            ->authenticate($credentials['name'], $credentials['pass'])) {
            $this->userFloodControl
                ->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name']));
            
            /** @var \Drupal\user\UserInterface $user */
            $user = $this->userStorage
                ->load($uid);
            $this->userLoginFinalize($user);
            // Send basic metadata about the logged in user.
            $response_data = [];
            if ($user->get('uid')
                ->access('view', $user)) {
                $response_data['current_user']['uid'] = $user->id();
            }
            if ($user->get('roles')
                ->access('view', $user)) {
                $response_data['current_user']['roles'] = $user->getRoles();
            }
            if ($user->get('name')
                ->access('view', $user)) {
                $response_data['current_user']['name'] = $user->getAccountName();
            }
            $response_data['csrf_token'] = $this->csrfToken
                ->get('rest');
            $logout_route = $this->routeProvider
                ->getRouteByName('user.logout.http');
            // Trim '/' off path to match \Drupal\Core\Access\CsrfAccessCheck.
            $logout_path = ltrim($logout_route->getPath(), '/');
            $response_data['logout_token'] = $this->csrfToken
                ->get($logout_path);
            $encoded_response_data = $this->serializer
                ->encode($response_data, $format);
            return new Response($encoded_response_data);
        }
        $flood_config = $this->config('user.flood');
        if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) {
            $this->userFloodControl
                ->register('user.http_login', $flood_config->get('user_window'), $identifier);
        }
        // Always register an IP-based failed login event.
        $this->userFloodControl
            ->register('user.failed_login_ip', $flood_config->get('ip_window'));
        throw new BadRequestHttpException('Sorry, unrecognized username or password.');
    }
    
    /**
     * Resets a user password.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The request.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *   The response object.
     */
    public function resetPassword(Request $request) {
        $format = $this->getRequestFormat($request);
        $content = $request->getContent();
        $credentials = $this->serializer
            ->decode($content, $format);
        // Check if a name or mail is provided.
        if (!isset($credentials['name']) && !isset($credentials['mail'])) {
            throw new BadRequestHttpException('Missing credentials.name or credentials.mail');
        }
        // Load by name if provided.
        if (isset($credentials['name'])) {
            $users = $this->userStorage
                ->loadByProperties([
                'name' => trim($credentials['name']),
            ]);
        }
        elseif (isset($credentials['mail'])) {
            $users = $this->userStorage
                ->loadByProperties([
                'mail' => trim($credentials['mail']),
            ]);
        }
        
        /** @var \Drupal\Core\Session\AccountInterface $account */
        $account = reset($users);
        if ($account && $account->id()) {
            if ($this->userIsBlocked($account->getAccountName())) {
                throw new BadRequestHttpException('The user has not been activated or is blocked.');
            }
            // Send the password reset email.
            $mail = _user_mail_notify('password_reset', $account);
            if (empty($mail)) {
                throw new BadRequestHttpException('Unable to send email. Contact the site administrator if the problem persists.');
            }
            else {
                $this->logger
                    ->notice('Password reset instructions mailed to %name at %email.', [
                    '%name' => $account->getAccountName(),
                    '%email' => $account->getEmail(),
                ]);
                return new Response();
            }
        }
        // Error if no users found with provided name or mail.
        throw new BadRequestHttpException('Unrecognized username or email address.');
    }
    
    /**
     * Verifies if the user is blocked.
     *
     * @param string $name
     *   The username.
     *
     * @return bool
     *   TRUE if the user is blocked, otherwise FALSE.
     */
    protected function userIsBlocked($name) {
        return user_is_blocked($name);
    }
    
    /**
     * Finalizes the user login.
     *
     * @param \Drupal\user\UserInterface $user
     *   The user.
     */
    protected function userLoginFinalize(UserInterface $user) {
        user_login_finalize($user);
    }
    
    /**
     * Logs out a user.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *   The response object.
     */
    public function logout() {
        $this->userLogout();
        return new Response(NULL, 204);
    }
    
    /**
     * Logs the user out.
     */
    protected function userLogout() {
        user_logout();
    }
    
    /**
     * Checks whether a user is logged in or not.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *   The response.
     */
    public function loginStatus() {
        if ($this->currentUser()
            ->isAuthenticated()) {
            $response = new Response(self::LOGGED_IN);
        }
        else {
            $response = new Response(self::LOGGED_OUT);
        }
        $response->headers
            ->set('Content-Type', 'text/plain');
        return $response;
    }
    
    /**
     * Gets the format of the current request.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The current request.
     *
     * @return string
     *   The format of the request.
     */
    protected function getRequestFormat(Request $request) {
        $format = $request->getRequestFormat();
        if (!in_array($format, $this->serializerFormats)) {
            throw new BadRequestHttpException("Unrecognized format: {$format}.");
        }
        return $format;
    }
    
    /**
     * Enforces flood control for the current login request.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The current request.
     * @param string $username
     *   The user name sent for login credentials.
     */
    protected function floodControl(Request $request, $username) {
        $flood_config = $this->config('user.flood');
        if (!$this->userFloodControl
            ->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
            throw new AccessDeniedHttpException('Access is blocked because of IP based flood prevention.', NULL, Response::HTTP_TOO_MANY_REQUESTS);
        }
        if ($identifier = $this->getLoginFloodIdentifier($request, $username)) {
            // 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->userFloodControl
                ->isAllowed('user.http_login', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
                if ($flood_config->get('uid_only')) {
                    $error_message = sprintf('There have been more than %s failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', $flood_config->get('user_limit'));
                }
                else {
                    $error_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
                }
                throw new AccessDeniedHttpException($error_message, NULL, Response::HTTP_TOO_MANY_REQUESTS);
            }
        }
    }
    
    /**
     * Gets the login identifier for user login flood control.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The current request.
     * @param string $username
     *   The username supplied in login credentials.
     *
     * @return string
     *   The login identifier or if the user does not exist an empty string.
     */
    protected function getLoginFloodIdentifier(Request $request, $username) {
        $flood_config = $this->config('user.flood');
        $accounts = $this->userStorage
            ->loadByProperties([
            'name' => $username,
            'status' => 1,
        ]);
        if ($account = reset($accounts)) {
            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();
            }
            return $identifier;
        }
        return '';
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 3
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 1
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 1
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 3
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 1
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 1
ControllerBase::redirect protected function Returns a redirect response object for the specified route.
ControllerBase::state protected function Returns the state storage service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 17
MessengerTrait::messenger public function Gets the messenger. 17
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UserAuthenticationController::$csrfToken protected property The CSRF token generator.
UserAuthenticationController::$logger protected property A logger instance.
UserAuthenticationController::$routeProvider protected property The route provider.
UserAuthenticationController::$serializer protected property The serializer.
UserAuthenticationController::$serializerFormats protected property The available serialization formats.
UserAuthenticationController::$userAuth protected property The user authentication.
UserAuthenticationController::$userFloodControl protected property The user flood control service.
UserAuthenticationController::$userStorage protected property The user storage.
UserAuthenticationController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
UserAuthenticationController::floodControl protected function Enforces flood control for the current login request.
UserAuthenticationController::getLoginFloodIdentifier protected function Gets the login identifier for user login flood control.
UserAuthenticationController::getRequestFormat protected function Gets the format of the current request.
UserAuthenticationController::LOGGED_IN constant String sent in responses, to describe the user as being logged in.
UserAuthenticationController::LOGGED_OUT constant String sent in responses, to describe the user as being logged out.
UserAuthenticationController::login public function Logs in a user.
UserAuthenticationController::loginStatus public function Checks whether a user is logged in or not.
UserAuthenticationController::logout public function Logs out a user.
UserAuthenticationController::resetPassword public function Resets a user password.
UserAuthenticationController::userIsBlocked protected function Verifies if the user is blocked.
UserAuthenticationController::userLoginFinalize protected function Finalizes the user login.
UserAuthenticationController::userLogout protected function Logs the user out.
UserAuthenticationController::__construct public function Constructs a new UserAuthenticationController object.

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