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.
kint()
I don't know but maybe this instruction tries to echo something ?jwt
auth method to your route?_auth: ['jwt_auth', 'cookie']