2

I'm just looking into adding some more data to the Drupal 8 API user login response.

curl --header "Content-type: application/json" --request POST --data '{"name":"username", "pass":"password"}' http://drupal8.localhost/user/login?_format=json

The response is like:

{
    "current_user": {
        "uid": "100",
        "roles": [
            "authenticated"
        ],
        "name": "username"
    },
    "csrf_token": "4gRg8XElB1jRp3xu1a4vm2oYiTCXLaoO8Ii72Dwe1kd",
    "logout_token": "gnUt53JZcM7KEFizLOFe996DTXgEqj7RyzhMwsmUssZ"
}

How can one add additional data to the user login API response?

I've looked into adding a custom Normalizer, but seems a bit tricky to intercept the /user/login response and curious if any other suggestions on it, thanks.

2 Answers 2

4

OK, I'd like to post one more answer approach, using the response event listener (with thanks to @Berdir). This is the one I'm using in the end as it avoids duplicating the login function logic. The previous answer may still be useful if you want to alter the actual login logic flow as well.

The event subscriber class

<?php

namespace Drupal\example\Listener;

use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Serialization\Xml;
use Drupal\Component\Serialization\Json;

/**
 * Class ExampleLoginListener.
 *
 * @package Drupal\example
 */
class ExampleLoginListener implements EventSubscriberInterface
{

  /**
   * @var path.current service
   */
  private $currentPath;
  /**
   * @var jwt.authentication.jwt service
   */
  private $jwtAuth;

  /**
   * Constructor with dependency injection
   */
  public function __construct($currentPath, $JwtAuth) {
    $this->currentPath = $currentPath;
    $this->jwtAuth = $JwtAuth;
  }

  /**
   * Add JWT access token to user login API response
   */
  public function onHttpLoginResponse(FilterResponseEvent $event) {
    // Halt if not user login request
    if ($this->currentPath->getPath() !== '/user/login') {
      return;
    }
    // Get response
    $response = $event->getResponse();
    // Ensure not error response
    if ($response->getStatusCode() !== 200) {
      return;
    }
    // Get request
    $request = $event->getRequest();
    // Just handle JSON format for now
    if ($request->query->get('_format') !== 'json') {
      return;
    }
    // Decode and add JWT token
    if ($content = $response->getContent()) {
      if ($decoded = Json::decode($content)) {
        // Add JWT access_token
        $access_token = $this->jwtAuth->generateToken();
        $decoded['access_token'] = $access_token;
        // Set new response JSON
        $response->setContent(Json::encode($decoded));
        $event->setResponse($response);
      }
    }
  }

  /**
   * The subscribed events.
   */
  public static function getSubscribedEvents() {
    $events = [];
    $events[KernelEvents::RESPONSE][] = ['onHttpLoginResponse'];
    return $events;
  }

}

and the example.services.yml entry

services:
  example.login_response_listener:
    class: '\Drupal\example\Listener\ExampleLoginListener'
    arguments: ['@path.current', '@jwt.authentication.jwt']
    tags:
      - { name: event_subscriber }

In this way, we can add the JWT access_token to the /user/login?_format=json API response.

2

OK, after some more study, I've found a reasonable solution. Posting here for the benefit of others. This one overrides the login route controller to add the custom data.

/**
 * @file
 * Contains \Drupal\example\Routing\RouteSubscriber.
 */

namespace Drupal\example\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {
  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
    // Alter user/login API route to return more data
    if ($route = $collection->get('user.login.http')) {
      $route->setDefaults(array(
        '_controller' => '\Drupal\example\Controller\ExampleUserAuthenticationController::login',
      ));
    }
  }
}

and then in the custom class handler

use Drupal\user\Controller\UserAuthenticationController;

class ExampleUserAuthenticationController extends UserAuthenticationController implements ContainerInjectionInterface {

  /* Add the custom login request handler with extra response data here */
  public function login(Request $request) {
    // ...
    $response_data['foo'] = 'bar';
    // ...
  }

}

It would be nice if it was possible to alter the response data only, without the login function override. However, this approach is working for me.

2
  • You can alter responses with the response event, but you will then already get the json string I think and would have to unserialize and re-serialize that. I think this is the easiest approach for this, instead of altering the default route, you could also define your own
    – Berdir
    Commented Mar 7, 2018 at 14:28
  • Hi @Berdir thanks for the suggestion, I will look into the response event data alter approach. Commented Mar 8, 2018 at 4:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.