'Symfony3 sub request lose of session
I experience a strange behavior when doing sub requests with Symfony 3.
The context
I think it's better to explain why I do what leads to the problem, it may help to find a solution. But you can skip this part if it's too much read.
I've developped kind of a clone of Api Platform, not a fork, but an inspired rewrite. One of the functionality I wanted to add is the ability to call the Api from the server side.
The Api does a lot of a work by itself (works very well with relations for example) so it's very useful to let it do the work of persisting / filtering, etc, even for a classic server side app (no ajax calls).
The simplest way I've found to do this was to do sub requests. My current implementation looks like this when used :
// To get a collection of articles (output as array)
$this->proxy->get(Article::class)->collection($page, $itemsPerPages, $filters, $ordering)->asArray();
// To get a single article (output as entities)
$this->proxy->get(Article::class)->item('a2Ck2')->asObject();
// To persist an article (output as entities)
$this->proxy->persist(Article::class)->item(['title' => 'Super article'])->asObject();
You get the idea.
When you call a as* method, the request is executed, and the code to do so is very simple :
// Proxy.php
public function execute(Request $request, Response &$response = null)
{
$response = $this->kernel->handle($request);
[...]
// Handle the response status code, the content etc. Not important here.
}
The request given in parameter of the execute method is built like this (here the GET builder is shown):
// GetRequestBuilder.php
protected function createRequest(): Request
{
$currentRequest = $this->getCurrentRequest();
$request = Request::create(
$this->generateUri([...]),
Request::METHOD_GET,
[],
$currentRequest->cookies->all()
);
return $request;
}
The problem
A problem occurs when I have multiple firewalls and when I do a sub request that matches a route in a different firewall than the one of the original request.
For the following security configuration :
# app/config/security.yml
security:
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
providers:
main:
id: app.main_user_provider
doctrine:
entity:
class: AppBundle:User
property: username
firewalls:
backend:
pattern: ^/admin
anonymous: ~
logout:
path: /admin/logout
target: /admin/login
invalidate_session: true
stateless: false
guard:
entry_point: app.backend.login_form_authenticator
authenticators: [app.backend.login_form_authenticator]
frontend:
pattern: ^/
anonymous: ~
logout:
path: /logout
target: /login
invalidate_session: true
stateless: false
guard:
entry_point: app.frontend.login_form_authenticator
authenticators: [app.frontend.login_form_authenticator]
If I do a sub request from an admin route to a route matching the frontend firewall, I lose my admin session.
If I dump the session before and after the Api call, it gives me the following :
array (size=3)
'_security.backend.target_path' => string 'http://localhost/admin/api/formation/categories' (length=51)
'_csrf/form' => string 'wt9Js9b8deT00XanUgEq23qXFqY8uHrt_j5i6D9Btj8' (length=43)
'_security_backend' => string 'C:67:"Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken":1786:{a:2:{i:0;s:7:"backend";i:1;s:174'... (length=1868)
And after the sub request :
array (size=2)
'_security.backend.target_path' => string 'http://localhost/admin/api/formation/categories' (length=51)
'_csrf/form' => string 'wt9Js9b8deT00XanUgEq23qXFqY8uHrt_j5i6D9Btj8' (length=43)
The _security_backend key is gone, and so is my session.
If I don't copy the cookies of the original request to the sub request, the session is not lost, but I have a problem if the Api route is secured:
// GetRequestBuilder.php
protected function createRequest(): Request
{
$currentRequest = $this->getCurrentRequest();
$request = Request::create(
$this->generateUri([...]),
Request::METHOD_GET,
[],
// Removing this line and replace it with an empty array solves the problem of loosing the session.
// But if the target route is behind the "backend" firewall, the sub request will be redirected to the login page.
$currentRequest->cookies->all()
);
return $request;
}
My questions
1) Do you see think this method of doing sub requests is robust enough or do you know a better way to achieve the same result?
On a side note: I don't do CURL or any other way to do real http requests because I want the ability to get entities (actual objects) as result of the Api call.
2) Do you know a way to prevent the original session to be lost if the sub request matches a route with a different firewall? As I understand it right now, the only way I see would be to detect (no idea how..) if the target route is on the same firewall or not, and to only copy the cookies if so.. Seems hard to do and very hard to have stable. And it looks dirty as **.
Any idea would be much appreciated.
Thanks for your help.
Solution 1:[1]
I post my own answer because I found an ugly way that seems to work to handle the case, but I feel really dirty and I would be greatful if someone can think of a clean way to handle this case.
So the solution is to "backup" session data before the request and restore it after... I did it like this:
// Proxy.php
public function execute(Request $request, Response &$response = null)
{
try {
$this->saveSession();
$response = $this->kernel->handle($request);
[...]
// Handle the response status code, the content etc. Not important here.
} finally {
$this->restoreSession();
}
}
/**
* Saves the current session.
*/
private function saveSession()
{
$this->sessionStack[] = (array)$this->session->all();
}
/**
* Restores the latest session saved using saveSession().
*/
private function restoreSession()
{
$data = array_pop($this->sessionStack);
$this->session->clear();
foreach ($data as $k => $v) {
$this->session->set($k, $v);
}
}
And, of course, I still copy cookies of the current request in the sub-request.
It's very basic but I seems to work (for my current case at least). I have no idea of the side effects of copying session data like this, but I can guess it's a bad thing.
It only works because I need to keep the _security_backend key which is a serialized object, so perfectly "backupable" as a string. But if in future cases the session holds real objects that are being modified in the sub-request, this will not work at all.
And what about performance if the session holds a lot of data? hmm
So if anyone of you have a better solution, I'm all ears :)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Stnaire |
