diff --git a/core/modules/rest/src/Plugin/ResourceBase.php b/core/modules/rest/src/Plugin/ResourceBase.php
index 33cb3aa..4e77eb9 100644
--- a/core/modules/rest/src/Plugin/ResourceBase.php
+++ b/core/modules/rest/src/Plugin/ResourceBase.php
@@ -64,7 +64,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id,
       $plugin_definition,
       $container->getParameter('serializer.formats'),
-      $container->get('logger.factory')->get('rest')
+      $container->get('logger.channel.rest')
     );
   }
 
diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php b/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php
new file mode 100644
index 0000000..9fe24c4
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Flood\FloodInterface;
+use Drupal\rest\ResourceResponse;
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\user\UserAuthInterface;
+use Drupal\user\UserInterface;
+use Drupal\user\UserStorageInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Provides a resource to let users log in.
+ *
+ * @RestResource(
+ *   id = "user_login",
+ *   label = @Translation("User login"),
+ *   uri_paths = {
+ *     "https://www.drupal.org/link-relations/create" = "/user/login",
+ *   },
+ * )
+ */
+class UserLoginResource extends ResourceBase {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The flood controller.
+   *
+   * @var \Drupal\Core\Flood\FloodInterface
+   */
+  protected $flood;
+
+  /**
+   * 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;
+
+  /**
+   * Constructs a new UserLoginResource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Flood\FloodInterface $flood
+   *   The flood controller.
+   * @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.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory, FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+    $this->configFactory = $config_factory;
+    $this->flood = $flood;
+    $this->userStorage = $user_storage;
+    $this->csrfToken = $csrf_token;
+    $this->userAuth = $user_auth;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.channel.rest'),
+      $container->get('config.factory'),
+      $container->get('flood'),
+      $container->get('entity.manager')->getStorage('user'),
+      $container->get('csrf_token'),
+      $container->get('user.auth')
+    );
+  }
+
+  /**
+   * Responds to user login POST requests and logs in a user.
+   *
+   * @param array $credentials
+   *   The login credentials.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The HTTP response object.
+   */
+  public function post($credentials) {
+    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.');
+    }
+
+    if (!$this->isFloodBlocked()) {
+      throw new BadRequestHttpException('Blocked.');
+    }
+
+    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'])) {
+      /** @var \Drupal\user\UserInterface $user */
+      $user = $this->userStorage->load($uid);
+      $this->userLoginFinalize($user);
+
+      // Send basic metadata about the logged in user.
+      $response_data = [
+        'current_user' => [
+          'uid' => $user->id(),
+          'roles' => $user->getRoles(),
+          'name' => $user->getAccountName(),
+        ],
+        'csrf_token' => $this->csrfToken->get('rest'),
+      ];
+
+      $response = new ResourceResponse($response_data);
+      return $response->addCacheableDependency($user);
+    }
+
+    $this->flood->register('rest.login_cookie', $this->configFactory->get('user.flood')->get('user_window'));
+    throw new BadRequestHttpException('Sorry, unrecognized username or password.');
+  }
+
+  /**
+   * Verifies if the user is blocked.
+   *
+   * @param string $name
+   *   The username.
+   *
+   * @return bool
+   *   Returns 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);
+  }
+
+  /**
+   * Checks for flooding.
+   *
+   * @return bool
+   *   TRUE if the user is allowed to proceed, FALSE otherwise.
+   */
+  protected function isFloodBlocked() {
+    $config = $this->configFactory->get('user.flood');
+    $limit = $config->get('user_limit');
+    $interval = $config->get('user_window');
+
+    return $this->flood->isAllowed('rest.login_cookie', $limit, $interval);
+  }
+
+}
diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLoginStatusResource.php b/core/modules/rest/src/Plugin/rest/resource/UserLoginStatusResource.php
new file mode 100644
index 0000000..97cdc49
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/UserLoginStatusResource.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\rest\ResourceResponse;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a resource to check whether the current user is logged in.
+ *
+ * @RestResource(
+ *   id = "user_login_status",
+ *   label = @Translation("User login status"),
+ *   uri_paths = {
+ *     "canonical" = "/user/login/status"
+ *   }
+ * )
+ */
+class UserLoginStatusResource extends ResourceBase {
+
+  /**
+   * String sent in responses, to describe the user as being logged in.
+   *
+   * @var string
+   */
+  const LOGGED_IN = 'LOGGED_IN';
+
+  /**
+   * String sent in responses, to describe the user as being logged out.
+   *
+   * @var string
+   */
+  const LOGGED_OUT = 'LOGGED_OUT';
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a new UserLoginStatusResource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, AccountInterface $current_user) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.channel.rest'),
+      $container->get('current_user')
+    );
+  }
+
+  /**
+   * Responds to user login status GET requests.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   A resource response.
+   */
+  public function get() {
+    if ($this->currentUser->isAuthenticated()) {
+      $response = new ResourceResponse(self::LOGGED_IN);
+      $response->addCacheableDependency($this->currentUser);
+    }
+    else {
+      $response = new ResourceResponse(self::LOGGED_OUT);
+    }
+    return $response->addCacheableDependency((new CacheableMetadata())->setCacheMaxAge(0));
+  }
+
+}
diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLogoutResource.php b/core/modules/rest/src/Plugin/rest/resource/UserLogoutResource.php
new file mode 100644
index 0000000..58228b3
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/UserLogoutResource.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\rest\ResourceResponse;
+
+/**
+ * Provides a resource to let the currently logged in user log out.
+ *
+ * @RestResource(
+ *   id = "user_logout",
+ *   label = @Translation("User logout"),
+ *   uri_paths = {
+ *     "https://www.drupal.org/link-relations/create" = "/user/logout",
+ *   }
+ * )
+ */
+class UserLogoutResource extends ResourceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function routes() {
+    $routes = parent::routes();
+    $route_name = strtr($this->pluginId, ':', '.');
+    $routes->get("$route_name.POST")->addRequirements([
+      '_user_is_logged_in' => 'TRUE',
+    ]);
+
+    return $routes;
+  }
+
+
+  /**
+   * Responds to user logout POST requests.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The response.
+   */
+  public function post() {
+    $this->userLogout();
+    return new ResourceResponse(NULL, 204);
+  }
+
+  /**
+   * Logs the user out.
+   */
+  protected function userLogout() {
+    user_logout();
+  }
+
+}
diff --git a/core/modules/rest/src/Plugin/rest/resource/UserPasswordResetResource.php b/core/modules/rest/src/Plugin/rest/resource/UserPasswordResetResource.php
new file mode 100644
index 0000000..308ab6d
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/UserPasswordResetResource.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\rest\ResourceResponse;
+use Drupal\user\UserStorageInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Provides a resource for resetting a user's password.
+ *
+ * @RestResource(
+ *   id = "user_password_reset",
+ *   label = @Translation("Password reset"),
+ *   uri_paths = {
+ *     "https://www.drupal.org/link-relations/create" = "/user/password/{name}/{langcode}",
+ *   },
+ * )
+ */
+class UserPasswordResetResource extends ResourceBase {
+
+  /**
+   * The user storage.
+   *
+   * @var \Drupal\user\UserStorageInterface
+   */
+  protected $userStorage;
+
+  /**
+   * Constructs a new UserPasswordResetResource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\user\UserStorageInterface $user_storage
+   *   The user storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, UserStorageInterface $user_storage) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+
+    $this->userStorage = $user_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.channel.rest'),
+      $container->get('entity_type.manager')->getStorage('user')
+    );
+  }
+
+  /**
+   * Responds to user password reset POST requests.
+   *
+   * @param string $name
+   *   The username or email address, that should be reset.
+   * @param string|null $langcode
+   *   (optional) Language code to use for the notification, overriding account
+   *   language.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The HTTP response.
+   */
+  public function post($name, $langcode = NULL) {
+    $name = trim($name);
+
+    if (!$account = $this->loadUserByNameOrEmail($name)) {
+      throw new BadRequestHttpException(new FormattableMarkup('%name is not recognized as a username or an email address.', ['%name' => $name]));
+    }
+
+    $mail = $this->userMailNotify($account, $langcode);
+    if (empty($mail)) {
+      throw new BadRequestHttpException('The email with password reset instructions could not be sent.');
+    }
+
+    $this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getDisplayName(), '%email' => $account->getEmail()]);
+    return new ResourceResponse('Further instructions have been sent to your email address.', 200, []);
+  }
+
+  /**
+   * Conditionally creates and sends a notification email.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user object of the account being notified. Must contain at
+   *   least the fields 'uid', 'name', and 'mail'.
+   * @param string $langcode
+   *   (optional) Language code to use for the notification, overriding account
+   *   language.
+   *
+   * @return array
+   *   An array containing various information about the message.
+   *   See \Drupal\Core\Mail\MailManagerInterface::mail() for details.
+   */
+  protected function userMailNotify(AccountInterface $account, $langcode = NULL) {
+    return _user_mail_notify('password_reset', $account, $langcode);
+  }
+
+  /**
+   * Loads a user by name or mail address.
+   *
+   * @param string $name
+   *   The username or mail address, that should be loaded.
+   *
+   * @return \Drupal\user\UserInterface|null
+   *   The loaded user or NULL.
+   */
+  protected function loadUserByNameOrEmail($name) {
+    // Try to load by email.
+    $accounts = $this->userStorage->loadByProperties(['mail' => $name, 'status' => '1']);
+    if ($accounts) {
+      return reset($accounts);
+    }
+
+    // No success, try to load by name.
+    $accounts = $this->userStorage->loadByProperties(['name' => $name, 'status' => '1']);
+    if ($accounts) {
+      return reset($accounts);
+    }
+  }
+
+}
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 9efdb35..bdf30e1 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -54,9 +54,16 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
       $method_settings = $config[$plugin][$request->getMethod()];
       if (empty($method_settings['supported_formats']) || in_array($format, $method_settings['supported_formats'])) {
         $definition = $resource->getPluginDefinition();
-        $class = $definition['serialization_class'];
         try {
-          $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method));
+          if (array_key_exists('serialization_class', $definition)) {
+            $unserialized = $serializer->deserialize($received, $definition['serialization_class'], $format, array('request_method' => $method));
+          }
+          // If the plugin does not specify a serialization class just decode
+          // the received data.
+          // Example: received JSON is decoded into a PHP array.
+          else {
+            $unserialized = $serializer->decode($received, $format, array('request_method' => $method));
+          }
         }
         catch (UnexpectedValueException $e) {
           $error['error'] = $e->getMessage();
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 099957e..0b4a99d 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -73,11 +73,13 @@ protected function setUp() {
    *   The body for POST and PUT.
    * @param string $mime_type
    *   The MIME type of the transmitted content.
+   * @param array $request_headers
+   *   Some additional request headers.
    *
-   * @return string
-   *   The content returned from the request.
+   * @return string The content returned from the request.
+   * The content returned from the request.
    */
-  protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
+  protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL, $request_headers = []) {
     if (!isset($mime_type)) {
       $mime_type = $this->defaultMimeType;
     }
@@ -118,10 +120,11 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
           CURLOPT_POSTFIELDS => $body,
           CURLOPT_URL => $url,
           CURLOPT_NOBODY => FALSE,
-          CURLOPT_HTTPHEADER => array(
+          CURLOPT_HTTPHEADER => array_merge(
+            array(
             'Content-Type: ' . $mime_type,
             'X-CSRF-Token: ' . $token,
-          ),
+          ), $request_headers),
         );
         break;
 
@@ -173,6 +176,9 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
 
     $this->verbose($method . ' request to: ' . $url .
       '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
+      '<hr />Request headers: ' . nl2br(print_r($curl_options[CURLOPT_HTTPHEADER], TRUE)) .
+      '<hr />Extra headers: ' . nl2br(print_r($request_headers, TRUE)) .
+      '<hr />Request body: ' . nl2br(print_r($body, TRUE)) .
       '<hr />Response headers: ' . nl2br(print_r($headers, TRUE)) .
       '<hr />Response body: ' . $this->responseBody);
 
diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php
index df99cc6..0919205 100644
--- a/core/modules/rest/src/Tests/ResourceTest.php
+++ b/core/modules/rest/src/Tests/ResourceTest.php
@@ -112,8 +112,10 @@ public function testUriPaths() {
     $manager = \Drupal::service('plugin.manager.rest');
 
     foreach ($manager->getDefinitions() as $resource => $definition) {
-      foreach ($definition['uri_paths'] as $key => $uri_path) {
-        $this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.');
+      if (isset($definition['uri_paths'])) {
+        foreach ($definition['uri_paths'] as $key => $uri_path) {
+          $this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.');
+        }
       }
     }
   }
diff --git a/core/modules/rest/src/Tests/UserLoginTest.php b/core/modules/rest/src/Tests/UserLoginTest.php
new file mode 100644
index 0000000..763712c
--- /dev/null
+++ b/core/modules/rest/src/Tests/UserLoginTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\rest\Tests;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Url;
+use Drupal\rest\Plugin\rest\resource\UserLoginStatusResource;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+/**
+ * Tests REST user login.
+ *
+ * @group rest
+ */
+class UserLoginTest extends RESTTestBase {
+
+  /**
+   * Test user session life cycle.
+   */
+  public function testLogin() {
+    // Enable the rest resources.
+    $format = 'json';
+    $auth = $this->defaultAuth;
+
+    $this->enableService('user_login', 'POST', $format, $auth);
+    $this->enableService('user_login_status', 'GET', $format, $auth);
+    $this->enableService('user_logout', 'POST');
+    $this->enableService('user_password_reset', 'POST');
+
+    $permissions[] = 'restful post user_login';
+    $account = $this->drupalCreateUser($permissions);
+    $name = $account->getUsername();
+    $pass = $account->pass_raw;
+
+    // Add login permission to anonymous user.
+    Role::load(RoleInterface::ANONYMOUS_ID)
+      ->grantPermission('restful post user_login')
+      ->grantPermission('restful get user_login_status')
+      ->save();
+
+    Role::load(RoleInterface::AUTHENTICATED_ID)
+      ->grantPermission('restful get user_login_status')
+      ->grantPermission('restful post user_logout')
+      ->save();
+
+
+    $url = Url::fromRoute('rest.user_login_status.GET.json');
+    $url->setRouteParameter('_format', 'json');
+    $this->httpRequest($url, 'GET', NULL, 'application/json');
+    $this->assertResponse(200);
+    $this->assertResponseBody('"' . UserLoginStatusResource::LOGGED_OUT . '"');
+
+    $request_body = [];
+    $this->httpRequest('/user/login/', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Missing credentials."}');
+
+    $request_body = ['pass' => $pass];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Missing credentials.name."}');
+
+    $request_body = ['name' => $name];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Missing credentials.pass."}');
+
+    // Blocked.
+    $account->block();
+    $account->save();
+    $request_body = ['name' => $name];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"The user has not been activated or is blocked."}');
+    $account->activate();
+    $account->save();
+
+    // Flooded.
+    $request_body = ['name' => $name];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Missing credentials.pass."}');
+
+    $request_body = ['name' => $name, 'pass' => 'garbage'];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Sorry, unrecognized username or password."}');
+
+    $request_body = ['name' => 'garbage', 'pass' => $pass];
+    $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"Sorry, unrecognized username or password."}');
+
+    $request_body = ['name' => $name, 'pass' => $pass];
+    $response = $this->httpRequest('/user/login', 'POST', json_encode($request_body), 'application/json');
+    $response = json_decode($response);
+    $this->assertEqual($name, $response->current_user->name, "The user name is correct.");
+
+    $url = Url::fromRoute('/user/login/status');
+    $url->setRouteParameter('_format', 'json');
+    $this->httpRequest($url, 'GET', NULL, 'application/json');
+    $this->assertResponse(200);
+    $this->assertResponseBody('"' . UserLoginStatusResource::LOGGED_IN . '"');
+
+    $request_body = ['name' => $name, 'pass' => $pass];
+    $this->httpRequest('/user/logout', 'POST', json_encode($request_body), 'application/json');
+    $this->assertResponse(204);
+
+    $url = Url::fromRoute('/user/login/status');
+    $url->setRouteParameter('_format', 'json');
+    $this->httpRequest($url, 'GET', NULL, 'application/json');
+    $this->assertResponse(200);
+    $this->assertResponseBody('"' . UserLoginStatusResource::LOGGED_OUT . '"');
+
+
+    $random_name = $this->randomMachineName();
+    $url = Url::fromRoute("/user/password/{$random_name}");
+    $url->setRouteParameter('_format', 'json');
+    $this->httpRequest($url, 'POST', NULL, 'application/json');
+    $this->assertResponse(400);
+    $this->assertResponseBody('{"error":"' . new FormattableMarkup('%name is not recognized as a username or an email address.', ['%name' => $random_name]) . '"}');
+
+    $url = Url::fromRoute("/user/password/{$name}");
+    $url->setRouteParameter('_format', 'json');
+    $this->httpRequest($url, 'POST', NULL, 'application/json');
+    $this->assertResponse(200);
+    $this->assertResponseBody('Further instructions have been sent to your email address.');
+  }
+
+}
