API Platform version(s) affected: 2.5.5
Description
Currently, AddFormatListener (priority 7) is executed after FirewallListener (priority 8). See registered kernel.request event listeners. This means the Security system cannot rely on Symfony\HttpFoundation\Request::getRequestFormat() as it has not yet been initialized.
When setting a response for the kernel.request event, the propagation is stopped. This means listeners with lower priority won't be executed.
How to reproduce
What if we had a Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface which relied on the request format to decide if it should return a HTTP 302 (see https://tools.ietf.org/html/rfc6749#section-4.1) redirect a user to login. But also supports https://tools.ietf.org/html/rfc6750#section-3 - notify an API client they must authenticate with a Bearer token?
public function start(
HttpFoundation\Request $request,
AuthenticationException $authException = null
): HttpFoundation\Response {
if ($request->getRequestFormat() === 'html') {
/**
* @see https://tools.ietf.org/html/rfc6749#section-4.1.1
*/
$state = sha1(openssl_random_pseudo_bytes(1000));
$stateKey = AuthorizationCodeAuthenticator::getStateKey($state);
$this->cache->save($stateKey, $state, 300);
$uri = $this->router->generate('auth_client_authorization_v2_authorize', [
'response_type' => 'code',
'client_id' => $this->oAuthClientId,
'state' => $state,
], UrlGeneratorInterface::ABSOLUTE_URL);
return new HttpFoundation\RedirectResponse($uri);
} else {
/**
* @see https://tools.ietf.org/html/rfc6750#section-3
*/
$data = [
'message' => 'Authentication Required',
];
$wwwAuthenticateHeader = sprintf(
'Bearer realm="%s"',
$this->realm
);
if (
$request->headers->has('Authorization')
&& 'Bearer' === substr($request->headers->get('Authorization'), 0, 6)
) {
$wwwAuthenticateHeader .= ', error="invalid_token"';
}
return new HttpFoundation\JsonResponse($data, HttpFoundation\Response::HTTP_UNAUTHORIZED, [
'WWW-Authenticate' => $wwwAuthenticateHeader,
]);
}
}
Possible Solution
Move AddFormatListener to a priority > 8
Additional Context
3b3f10d#r39354527
but we must double check that it has no bad side effects in term of security before changing this.
The side effects of running ApiPlatform\Core\EventListener\AddFormatListener are:
- the
Request::$formats are registered (Map from format names to mime types e.g. 'json' => ['application/json'])
- the
Request::$format is set (The format of the current request)
- A
NotAcceptableHttpException could be thrown (I think this is a good thing, content negotiation is generally cheaper than authentication)
When priority is < 8 the Request::$format is left null (unless manipulated outside of api-platform/core by another higher priority listener).
Since event dispatcher priorities are not strictly defined by Symfony, we should look to other projects for consensus on a good priority to use for AddFormatListener.
We should start a list of notable bundles that register kernel.request event listeners which might affect the chosen priority for AddFormatListener. Interoperability with other libraries is an important consideration.
Example order of event listeners in a standard symfony 5 project:
| Listener |
Priority |
Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure() |
2048 |
Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest() |
256 |
Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest() |
128 |
Symfony\Component\HttpKernel\EventListener\LocaleListener::setDefaultLocale() |
100 |
Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest() |
32 |
Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest() |
16 |
Symfony\Component\HttpKernel\EventListener\LocaleAwareListener::onKernelRequest() |
15 |
Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::configureLogoutUrlGenerator() |
8 |
Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelRequest() |
8 |
Sentry\SentryBundle\EventListener\RequestListener::onKernelRequest() |
1 |
Sentry\SentryBundle\EventListener\SubRequestListener::onKernelRequest() |
1 |
API Platform version(s) affected: 2.5.5
Description
Currently,
AddFormatListener(priority 7) is executed afterFirewallListener(priority 8). See registeredkernel.requestevent listeners. This means the Security system cannot rely onSymfony\HttpFoundation\Request::getRequestFormat()as it has not yet been initialized.How to reproduce
What if we had a
Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterfacewhich relied on the request format to decide if it should return a HTTP 302 (see https://tools.ietf.org/html/rfc6749#section-4.1) redirect a user to login. But also supports https://tools.ietf.org/html/rfc6750#section-3 - notify an API client they must authenticate with a Bearer token?Possible Solution
Move
AddFormatListenerto a priority> 8Additional Context
3b3f10d#r39354527
The side effects of running
ApiPlatform\Core\EventListener\AddFormatListenerare:Request::$formatsare registered (Map from format names to mime types e.g.'json' => ['application/json'])Request::$formatis set (The format of the current request)NotAcceptableHttpExceptioncould be thrown (I think this is a good thing, content negotiation is generally cheaper than authentication)When priority is
< 8theRequest::$formatis leftnull(unless manipulated outside ofapi-platform/coreby another higher priority listener).Since event dispatcher priorities are not strictly defined by Symfony, we should look to other projects for consensus on a good priority to use for
AddFormatListener.We should start a list of notable bundles that register
kernel.requestevent listeners which might affect the chosen priority forAddFormatListener. Interoperability with other libraries is an important consideration.Example order of event listeners in a standard symfony 5 project:
Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure()Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest()Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest()Symfony\Component\HttpKernel\EventListener\LocaleListener::setDefaultLocale()Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest()Symfony\Component\HttpKernel\EventListener\LocaleAwareListener::onKernelRequest()Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::configureLogoutUrlGenerator()Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelRequest()Sentry\SentryBundle\EventListener\RequestListener::onKernelRequest()Sentry\SentryBundle\EventListener\SubRequestListener::onKernelRequest()