0

My Controller is working with cookie auth but failing with JWT. This Controller is supposed to flag an entity for the logged-in user.

If I am using cookie auth, there are no errors and everything works as expected.

But when I try to use JWT, although the entity does get flagged correctly, I get the following error in the Drupal logs:

RuntimeException: Failed to start the session because headers have already been sent by "/app/vendor/symfony/http-foundation/Response.php" at line 377. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 150 of /app/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php)

How do I fix this error?

Here's how I'm using JWT auth in Postman:

POST http://example.com/api/group_add?_format=json

Headers:

  • Accept: application/vnd.api+json
  • Content-Type: application/vnd.api+json
  • Cache: no-cache
  • Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTgyMDk4MjAsImV4cCI6MTYyMzM5MzgyMCwiZHJ1cGFsIjp7InVpZCI6IjI3In19.5uDJMtokLXD6K63H5Ikb-F870EYFMrgE4mItTuTT3bI

Request body:

{
    "entity_id": "14"
}

As for the Controller, here's MYMODULE.routing.yml:

MYMOUDLE.api_flagging.http:
  path: '/api/group_add'
  defaults:
    _controller: '\Drupal\MYMODULE\Controller\ApiFlagging::flag'
  methods: [POST]
  requirements:
    _permission: 'view own commerce_order'
    _format: 'json'
  options:
    no_cache: 'TRUE'

Here's ApiFlagging.php:

<?php

namespace Drupal\MYMODULE\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\flag\FlagServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;

/**
 * Class ApiFlagging.
 *
 * Https://www.drupal.org/project/flag/issues/3091824#comment-13336379
 */
class ApiFlagging extends ControllerBase {

  const FLAG_ID = 'ABC';

  /**
   * The flag service.
   *
   * @var \Drupal\flag\FlagServiceInterface
   */
  protected $flagService;

  /**
   * The serializer.
   *
   * @var \Symfony\Component\Serializer\Serializer
   */
  protected $serializer;

  /**
   * The available serialization formats.
   *
   * @var array
   */
  protected $serializerFormats = [];

  /**
   * Constructs a new ApiFlagging object.
   */
  public function __construct(Serializer $serializer, array $serializer_formats, FlagServiceInterface $flag) {
    $this->serializer = $serializer;
    $this->serializerFormats = $serializer_formats;
    $this->flagService = $flag;
  }

  /**
   * {@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(
      $serializer,
      $formats,
      $container->get('flag')
    );
  }

  /**
   * Flagging.
   */
  public function flag(Request $request) {
    $format = $this->getRequestFormat($request);

    $content = $request->getContent();
    $flagData = $this->serializer->decode($content, $format);
    $flag = $this->flagService->getFlagById(self::FLAG_ID);
    $flaggableEntityTypeId = $flag->getFlaggableEntityTypeId();

    $my_goals = NULL;
    if (array_key_exists('goals', $flagData)) {
      $my_goals = $flagData['goals'];
    }

    $entity = \Drupal::entityTypeManager()
      ->getStorage($flaggableEntityTypeId)
      ->load($flagData['entity_id']);

    if ($my_goals === NULL) {
      return new JsonResponse([
        'error_message' => 'Goals not set.',
      ], 400);
    }

    try {
      /** @var \Drupal\flag\Entity\Flagging $flagging */
      $flag->set('field_goals', $my_goals);
      $flagging = $this->flagService->flag($flag, $entity);
    }
    catch (\LogicException $e) {
      $message = $e->getMessage();
      return new JsonResponse([
        'error_message' => $message,
      ], 400);
    }

    return new JsonResponse([
      'message' => 'flag success',
      'flagging_uuid' => $flagging->uuid(),
      'flagging_id' => $flagging->id(),
      'flag_id' => $flagging->getFlagId(),
    ]);
  }

  /**
   * 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;
  }

}

More info:

Here's the rest of the error:

#0 /app/web/core/lib/Drupal/Core/Session/SessionManager.php(164): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start()
#1 /app/web/core/lib/Drupal/Core/Session/SessionManager.php(195): Drupal\Core\Session\SessionManager->startNow()
#2 /app/vendor/symfony/http-foundation/Session/Session.php(196): Drupal\Core\Session\SessionManager->save()
#3 /app/web/core/lib/Drupal/Core/StackMiddleware/Session.php(60): Symfony\Component\HttpFoundation\Session\Session->save()
#4 /app/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#5 /app/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#6 /app/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#7 /app/vendor/asm89/stack-cors/src/Asm89/Stack/Cors.php(49): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#8 /app/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Asm89\Stack\Cors->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#9 /app/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#10 /app/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#11 /app/web/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#12 /app/web/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#13 {main}
.

When I test using my frontend app, I'm also getting the following error in the browser console:

Access to XMLHttpRequest at 'http://example.com/api/group_add' from origin 'http://localhost:8100' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

When I check the browser dev tools Network tab, I see that the preflight request to group_add has an Access-Control-Allow-Origin: http://localhost:8100 header, but the xhr request for group_add does not.

3
  • Did you check the stack and did you try without the kint() I don't know but maybe this instruction tries to echo something ?
    – Simon Vart
    Commented Apr 12, 2021 at 9:05
  • @SimonVart Removing kint has no effect. I updated the question with some more info; it's also showing a CORS error in my frontend app. Commented Apr 12, 2021 at 10:39
  • @PatrickKenny any difference if you add the jwt auth method to your route? _auth: ['jwt_auth', 'cookie'] Commented Apr 12, 2021 at 11:02

1 Answer 1

0

Ugh, I found the problem.

In a hook_flagging_insert(), I was redirecting users upon node creation:

$response = new RedirectResponse($redirect_url);
$response->send();

Removing the redirect eliminates the problem with jwt.

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.