I'm trying to build a simple login using symofny/security
package in Silex, but I have a small problem with authentication checking.
The structure:
/
/login
/admin
/login_check
/logout
In order to get to the /admin
route user needs to be authenticated, if he's not, he gets redirected to /login
form, which then, as recommended, is asking admin/login_check
for authentication (that's all provided by symofny's security firewalls).
The firewalls configuration:
'security.firewalls' => array(
'login' => array(
'pattern' => '^/login$',
),
'admin' => array(
'pattern' => '^/admin',
'http' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/admin/login'
),
'logout' => array(
'logout_path' => '/admin/logout',
'invalidate_session' => true
),
'users' => ...
),
)
Everything works fine, but the user can enter the /login
route even-though he's already authenticated, which is not that bad, but I'd like to avoid it. I tired to check the user authentication status, but I guess it does not work as I'm checking it on /login
controller, which is not in "secured" area of the website.
Here's the code:
public function index(Request $request, Application $app)
{
if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) {
return $app->redirect($app['url_generator']->generate('admin'));
}
return $app['twig']->render('login/index.twig', array(
'error' => $app['security.last_error']($request),
'last_username' => $app['session']->get('_security.last_username'),
));
}
That throws an error: The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.
So, the question, is there any way to do this using symfony/security
natively (without any walk-arounds)? or is it somehow possible to create the /login
route inside secured area with possibility to access it even-though the user is not logged in (e.g. make an exception for GET request) which would solve the problem?
UPDATE
I've also added the firewall configuration for /login
page with an option anonymous: true
, which resulted in not throwing an error anymore, but yet, when I'm logged in on the /admin
route, method isGranted('ROLE_ADMIN')
results in true
, whereas on /login
route it results in false
(I'm still logged in there).
You can easily understand the behavior of the security component by dumping the current token.
public function index(Request $request, Application $app)
{
var_dump($application['security.token_storage']->getToken());
}
When you don't set anonymous
option for login page (by default it is false
):
null
When you set anonymous
option for login page to true
:
object(Symfony\Component\Security\Core\Authentication\Token\AnonymousToken)
private 'secret' => string 'login' (length=5)
private 'user' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => string 'anon.' (length=5)
private 'roles' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) =>
array (size=0)
empty
private 'authenticated' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => boolean true
private 'attributes' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) =>
array (size=0)
empty
Following example describes why you are getting error in your initial code example.
To share security token between multiple firewalls you have to set the same Firewall Context.
SecurityServiceProvider
registers context listener for protected firewall you have declared with pattern security.context_listener.<name>
. In your example it is registered as security.context_listener.admin
. Thus context you want to use is named admin
.
Also a firewall context key is stored in session, so every firewall using it must set its stateless
option to false
.
What is more, to invoke authentication on login page the anonymous
option must be set to true
'security.firewalls' => array(
'login' => array(
'context' => 'admin',
'stateless' => false,
'anonymous' => true,
'pattern' => '^/login$',
),
'admin' => array(
'context' => 'admin',
'stateless' => false,
'pattern' => '^/admin',
'http' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/admin/login'
),
'logout' => array(
'logout_path' => '/admin/logout',
'invalidate_session' => true
),
'users' => ...
),
)
After the change, if you are logged in on admin panel and login page you will get an instance of Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
as a token and
Symfony\Component\Security\Core\Authentication\Token\AnonymousToken
if you are not logged in.
Because of that, you can safely check permission using $app['security.authorization_checker']->isGranted('ROLE_ADMIN')
and get the same results on both firewalls.