Drupal - API Resources for Custom login
Tags: drupal, php, rest, api
Published: 2022-07-18 22:29:22

Drupal 9 rest custom login resource, return session data to build cookie in frontend.
missing csrf (can be obtained at `/session/token`).

**Create rest plugin using Drush command.**
`$ drush generate plugin-rest-resource`
Or using an alias
`$ drush gen rest-resource`

This is a `POST` resource, so run `$ drush cr`
Using [Rest UI](https://www.drupal.org/project/restui) enable resource and add a permission for anonymous role.

```php
<?php

namespace Drupal\custom_rest_api\Plugin\rest\resource;

use Drupal\Core\Session\AccountProxyInterface;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Password\PasswordInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Represents Custom login resource records as resources.
 *
 * @RestResource (
 *   id = "custom_rest_api_custom_login_resource",
 *   label = @Translation("Custom login resource"),
 *   uri_paths = {
 *     "create" = "/api/custom/login"
 *   }
 * )
 *
 * @DCG
 * This plugin exposes database records as REST resources. In order to enable it
 * import the resource configuration into active configuration storage. You may
 * find an example of such configuration in the following file:
 * core/modules/rest/config/optional/rest.resource.entity.node.yml.
 * Alternatively you can make use of REST UI module.
 * @see https://www.drupal.org/project/restui
 * For accessing Drupal entities through REST interface use
 * \Drupal\rest\Plugin\rest\resource\EntityResource plugin.
 */
class CustomLoginResource extends ResourceBase {
  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  protected $sessionManager;

  protected $moduleHandler;

  protected $password;

  /**
   * Constructs a new CustomLoginResource 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\AccountProxyInterface $current_user
   *   A current user instance.
   */
  public function __construct(
    array $configuration,
          $plugin_id,
          $plugin_definition,
    array $serializer_formats,
    LoggerInterface $logger,
    AccountProxyInterface $current_user,
    SessionManagerInterface $session_manager,
    ModuleHandlerInterface $module_handler,
    PasswordInterface $password) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);

    $this->currentUser = $current_user;
    $this->sessionManager = $session_manager;
    $this->moduleHandler = $module_handler;
    $this->password = $password;
  }

  /**
   * {@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.factory')->get('exp_fs'),
      $container->get('current_user'),
      $container->get('session_manager'),
      $container->get('module_handler'),
      $container->get('password')
    );
  }

  /**
   * Responds to POST requests.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function post($data) {
    $this->validate($data);
    $pass_check = FALSE;
    $name = $data['name'];
    $pass = $data['pass'];

    $account = user_load_by_name(trim($name));
    if ($account) {
      $pass_check = $this->password->check(trim($pass), $account->getPassword());
    }
    else {
      $body = [
        'error' => 'Wrong username and/or password.',
      ];
    }

    if ($pass_check == FALSE) {
      $body = [
        'error' => 'Wrong username and/or password..',
      ];
    }
    else {
      $session = \Drupal::service('session');
      $session->migrate();
      $session->set('uid', $account->id());
      $this->moduleHandler->invokeAll('user_login', [$account]);
      user_login_finalize($account);

      $sess_name = $this->sessionManager->getName();
      $sess_id = $this->sessionManager->getId();

      $body = [
        'sess_name' => $sess_name,
        'sess_id' => $sess_id,
        'current_user' => [
          'name' => $account->getAccountName(),
          'uid' => $account->id(),
          'roles' => $account->getRoles(),
        ],
      ];
    }

    return new ModifiedResourceResponse($body, 200);
  }

  /**
   * Validates incoming record.
   *
   * @param mixed $record
   *   Data to validate.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
   */
  protected function validate($record) {
    if (!is_array($record) || count($record) == 0) {
      throw new BadRequestHttpException(t('No record content received'));
    }

    if (empty($record['name'])) {
      throw new BadRequestHttpException(t('name id is required'));
    }
    if (empty($record['pass'])) {
      throw new BadRequestHttpException(t('Password date is required'));
    }
  }

}
```