This problem has been reproduced with Symfony 3.3.17 and 3.4.9
I have a custom authenticaton provider that ties together a legacy application and a Symfony application:
app/config/security.yml
security:
providers:
zog:
id: app.zog_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: /logout
target: /
guard:
authenticators:
- app.legacy_token_authenticator
- app.token_authenticator
entry_point: app.legacy_token_authenticator
src/AppBundle/Security/LegacyTokenAuthenticator:
class LegacyTokenAuthenticator extends AbstractGuardAuthenticator
{
private $session;
private $router;
public function __construct(
RouterInterface $router,
SessionInterface $session,
$environment
) {
if (session_status() != PHP_SESSION_ACTIVE) {
if ($environment != 'test'){
session_start();
}
$session->start();
$this->setSession($session);
}
//if (!$session->isStarted()) {
//}
$this->router = $router;
}
/**
* @return mixed
*/
public function getSession()
{
return $this->session;
}
/**
* @param mixed $session
*/
public function setSession($session)
{
$this->session = $session;
}
/**
* Called on every request. Return whatever credentials you want,
* or null to stop authentication.
*/
public function getCredentials(Request $request)
{
$session = $this->getSession();
if (isset($_SESSION['ADMIN_logged_in']) && intval($_SESSION['ADMIN_logged_in'])){
return $_SESSION['ADMIN_logged_in'];
}
return;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUserId($credentials);
}
public function checkCredentials($credentials, UserInterface $user)
{
return $user->getUsername() == $credentials;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return null;
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->router->generate('app_security_login');
return new RedirectResponse($url);
}
public function supportsRememberMe()
{
return false;
}
}
src/AppBundle/Security/TokenAuthenticator:
class TokenAuthenticator extends AbstractGuardAuthenticator
{
/**
* @var \Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* Default message for authentication failure.
*
* @var string
*/
private $failMessage = 'Invalid credentials';
/**
* @var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* Creates a new instance of FormAuthenticator
*/
public function __construct(
RouterInterface $router,
SessionInterface $session,
$environment,
UserPasswordEncoderInterface $passwordEncoder
) {
$this->passwordEncoder = $passwordEncoder;
$this->router = $router;
if (session_status() != PHP_SESSION_ACTIVE) {
if ($environment != 'test') {
session_start();
}
$session->start();
}
}
/**
* {@inheritdoc}
*/
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/security/login' || !$request->isMethod('POST')) {
return;
}
return ['username' => $request->request->get('username'), 'password' => $request->request->get('password')];
}
/**
* {@inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
try {
return $userProvider->loadUserByUsername($credentials['username']);
} catch (UsernameNotFoundException $e) {
throw new CustomUserMessageAuthenticationException(
$e->getMessage() != '' ?$e->getMessage():$this->failMessage
);
}
}
/**
* {@inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
if ($this->passwordEncoder->isPasswordValid($user, $credentials['password'])) {
return true;
}
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
/**
* {@inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$_SESSION['ADMIN_logged_in'] = $token->getUser()->getUsername();
if ($_SESSION['legacy_page_requested'] ?? '/'){
$url = $_SESSION['legacy_page_requested'] ?? '/';
}else{
$url = '/workflow_detailv2view.php';
}
unset($_SESSION['legacy_page_requested']);
return new RedirectResponse($url);
}
/**
* {@inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('app_security_login');
return new RedirectResponse($url);
}
/**
* {@inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->router->generate('app_security_login');
return new RedirectResponse($url);
}
/**
* {@inheritdoc}
*/
public function supportsRememberMe()
{
return false;
}
}
What I find is that this system works fine. However a new feature in a legacy page is running 2 Symfony async application requests which are overlapping.
In this case what happens is the 1st request shows 2 Session Cookie
Request URL: https://somedomain.com/system/staff_meeting/edit/1
Request Method: GET
Status Code: 200 OK
Remote Address: 222.154.225.22:443
Referrer Policy: no-referrer-when-downgrade
Cache-Control: max-age=0, must-revalidate, private
Cache-Control: no-store, no-cache, must-revalidate
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Date: Wed, 13 Jun 2018 21:57:19 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=15, max=90
Server: Apache/2.4.6 (CentOS) mpm-itk/2.4.7-04 OpenSSL/1.0.2k-fips PHP/7.0.20
Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/
Set-Cookie: PHPSESSID=tn7jhi5n2iu16le1os971vn024; path=/
Transfer-Encoding: chunked
X-Powered-By: PHP/7.0.20
and the second request is being logged out:
Request URL: https://somedomain.com/system/staff_meeting/edit/1
Request Method: GET
Status Code: 302 Found
Remote Address: 222.154.225.22:443
Referrer Policy: no-referrer-when-downgrade
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: max-age=0, must-revalidate, private
Connection: Keep-Alive
Content-Length: 332
Content-Type: text/html; charset=UTF-8
Date: Thu, 14 Jun 2018 01:26:43 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive: timeout=15, max=90
Location: /system/security/login
Server: Apache/2.4.6 (CentOS) mpm-itk/2.4.7-04 OpenSSL/1.0.2k-fips PHP/7.0.20
Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/
X-Powered-By: PHP/7.0.20
I believe this must be some race condition in accessing the $_SESSION variable we use to hold together legacy system and Symfony application.
Any ideas how to resolve this issue?
make the following changes to your authenticators to keep the session management to symfony:
src/AppBundle/Security/LegacyTokenAuthenticator:
public function __construct(RouterInterface $router) {
$this->router = $router;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUsername($credentials);
}
public function getCredentials(Request $request)
{
$session = $request->getSession();
if ($session->has('ADMIN_logged_in') && intval($session->get('ADMIN_logged_in'))){
return $session->get('ADMIN_logged_in');
}
return null;
}
src/AppBundle/Security/TokenAuthenticator:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$session = $request->getSession();
$session->set('ADMIN_logged_in', $token->getUser()->getUsername());
if ($session->has('legacy_page_requested')) {
$url = $session->get('legacy_page_requested') ?? '/';
$session->remove('legacy_page_requested');
} else {
$url = '/workflow_detailv2view.php';
}
return new RedirectResponse($url);
}
I was able to circumvent the problem by setting
security.yml
security:
session_fixation_strategy: none
I am not sure however how the regeneration of the session and corresponding renaming of Cookie value can be fixed.
I'm still keen to hear other thoughts on this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With