I have problem with symfony2 (I have tried the same with Symfony 2.0 & Symfony 2.3 just to see if its a Symfony bug), I am loosing the security token in the next page load / redirect after authentication.
I have created custom authenticator for Symfony 2.3 to authenticate with a 3rd party service as specified here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
The application authenticates with an external service and sets the token in the callback URL '/success' and i can see from the debug bar that user is authenticated but when i go to '/' (which is under the same firewall) i am getting "A Token was not found in the SecurityContext." Error and user is no longer authenticated.
Here are the files:
security.yml
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
users:
entity: { class: AcmeStoreBundle:User, property: email }
firewalls:
login:
pattern: ^/login$
security: false
noa:
pattern: ^/
provider: users
noa: true
logout:
path: /logout
target: /login
access_control:
- { path: ^/success, roles: IS_AUTHENTICATED_ANONYMOUSLY }
NoaUserToken.php
<?php
namespace Acme\StoreBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class NoaUserToken extends AbstractToken
{
public $expires;
public $mobile;
public $email;
public function __construct(array $roles = array())
{
parent::__construct($roles);
parent::setAuthenticated(true);
}
public function getCredentials()
{
return '';
}
}
NoaProvider.php
<?php
namespace Acme\StoreBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Acme\StoreBundle\Security\Authentication\Token\NoaUserToken;
class NoaProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token)
{
$userEmail = $token->getUser();
$user = $this->userProvider->loadUserByUsername($userEmail);
if ($user && $this->validateToken($token->expires) && !$user->getHidden()) {
$authenticatedToken = new NoaUserToken($user->getRoles());
$authenticatedToken->expires = $token->expires;
$authenticatedToken->mobile = $token->mobile;
$authenticatedToken->email = $token->email;
$authenticatedToken->setUser($user);
$authenticatedToken->setAuthenticated(true);
return $authenticatedToken;
}
throw new AuthenticationException('The NOA authentication failed.');
}
protected function validateToken($expires)
{
// Check if the token has expired.
if (strtotime($expires) <= time()) {
return false;
}
}
public function supports(TokenInterface $token)
{
return $token instanceof NoaUserToken;
}
}
NoaListener.php
<?php
namespace Acme\StoreBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Acme\StoreBundle\Security\Authentication\Token\NoaUserToken;
class NoaListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (! preg_match('/^\/app_dev.php\/success/', $request->getRequestUri())) {
return;
}
if( $this->securityContext->getToken() ){
return;
}
try {
\NOA_Sso_Web::getInstance()->createSession();
}
catch (Exception $e) {
// Handle error situation here
}
if (isset($_SESSION['userInfo'])) {
$token = new NoaUserToken();
$token->setUser($_SESSION['userInfo']['email']);
$token->mobile = $_SESSION['userInfo']['mobileVerified'] ? $_SESSION['userInfo']['mobile'] : null;
$token->email = $_SESSION['userInfo']['emailVerified'] ? $_SESSION['userInfo']['email'] : null;
$token->expires = $_SESSION['tokenInfo']['expires'];
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
// Do nothing and go for the default 403
}
}
$this->securityContext->setToken(null);
// Deny authentication with a '403 Forbidden' HTTP response
$response = new Response();
$response->setStatusCode(403);
$event->setResponse($response);
}
}
NoaFactory.php
<?php
namespace Acme\StoreBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class NoaFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.noa.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('noa.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.noa.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('noa.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'noa';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
DefaultController.php
<?php
namespace Acme\StoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends Controller
{
public function indexAction()
{
$token = $this->container->get('security.context')->getToken();
// Not reached
print '<pre>';
print_r($token->getUser());
print '</pre>';
return $this->render('AcmeStoreBundle:Default:index.html.twig', array('name' => $token->getUser()->gerUsername()));
}
public function loginAction()
{
return $this->render('AcmeStoreBundle:Default:login.html.twig', array());
}
public function successAction()
{
$token = $this->container->get('security.context')->getToken();
$this->container->get('event_dispatcher')->dispatch(
SecurityEvents::INTERACTIVE_LOGIN,
new InteractiveLoginEvent($this->container->get('request'), $token)
);
// This prints the user object
print '<pre>';
print_r($token->getUser());
print '</pre>';
return new Response('<script>//window.top.refreshPage();</script>');
}
}
I have checked all similar questions in stackoverflow and spent around a week to solve this issue, any help is greatly appreciated.
Rather than using $_SESSION in NoaListener, you ought to be using the session interface on the request object. Symfony does its own session management and may ignore or overwrite your session (e.g. it's common to migrate sessions upon successful login to prevent session fixation attacks).
Use $request = $event->getRequest() as you already have, then $request->getSesssion()->get('userInfo'), etc.
Related
By default, it shows error "Invalid credentials.". I've already seen answers like "Go to translations, create security.en.yaml and type this:
# translations/security.en.yaml
'Invalid credentials.': 'Invalid email or password'
But how to create different errors? For example, "Invalid password" when password is wrong and "Email does not exists" when email is wrong. How to do it?
you must create custom authorization and exceptions.
example:
config/packages/security.yaml
security:
enable_authenticator_manager: true
...
providers:
...
firewalls:
...
client:
pattern: ^/
custom_authenticators:
- App\Security\ClientLoginFormAuthenticator
logout:
path: store.account.logout
target: store.home
access_control:
...
src/Security/ClientLoginFormAuthenticator.php
<?php
declare(strict_types=1);
namespace App\Security;
use App\Repository\UserRepository;
use App\Security\Exception\CustomException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use function in_array;
class ClientLoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
private const LOGIN_ROUTE = 'store.account.login';
public function __construct(private UserRepository $userRepository, private UrlGeneratorInterface $urlGenerator)
{}
public function supports(Request $request): bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function authenticate(Request $request): Passport
{
$password = $request->request->get('_password');
$username = $request->request->get('_username');
$csrfToken = $request->request->get('_csrf_token');
return new Passport(
new UserBadge($username, function ($userIdentifier) {
$user = $this->userRepository->findOneBy(['email' => $userIdentifier]);
if ($user && in_array('ROLE_CLIENT', $user->getRoles(), true)) {
return $user;
}
//next condition
if($user && $user->getEmail() === 'superadmin#example.com') {
throw new CustomException();
}
throw new BadCredentialsException(); //default exception
}),
new PasswordCredentials($password),
[new CsrfTokenBadge('authenticate', $csrfToken)]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
src/Security/Exception/CustomException.php
<?php
namespace App\Security\Exception;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class CustomException extends AuthenticationException
{
/**
* {#inheritdoc}
*/
public function getMessageKey()
{
return 'My Message.';
}
}
It works for me! :) Good luck!
I'm trying to upgrade a Symfony 4.3.6 application to v4.4.
The app uses oauth2-client-bundle to authenticate users with Keycloak. Consequently, users are never persisted in database.
Here is the security config:
security:
providers:
oauth:
id: knpu.oauth2.user_provider
firewalls:
main:
logout:
path: app.logout
anonymous: true
guard:
authenticators:
- App\Security\KeycloakAuthenticator
access_control:
- { path: ^/connect, roles: IS_AUTHENTICATED_ANONYMOUSLY }
-
path: ^/
allow_if: "'127.0.0.1' == request.getClientIp() or is_granted('IS_AUTHENTICATED_FULLY')"
And here is the KeycloakAuthenticator service:
<?php
namespace App\Security;
use App\Entity\User; // extends KnpU\OAuth2ClientBundle\Security\User\OAuthUser
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class KeycloakAuthenticator extends SocialAuthenticator
{
private $clientRegistry;
private $router;
public function __construct(ClientRegistry $clientRegistry, RouterInterface $router)
{
$this->clientRegistry = $clientRegistry;
$this->router = $router;
}
public function supports(Request $request)
{
return 'connect_keycloak_check' === $request->attributes->get('_route');
}
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getKeycloakClient());
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$keycloakUser = $this->getKeycloakClient()->fetchUserFromToken($credentials);
$user = new User($keycloakUser->getName(), ['IS_AUTHENTICATED_FULLY', 'ROLE_USER']);
$user->setEmail($keycloakUser->getEmail())
->setName($keycloakUser->getName())
->setLocale($keycloakUser->toArray()['locale'] ?? 'fr')
;
return $user;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// On success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->router->generate('app.keycloak_start'), Response::HTTP_TEMPORARY_REDIRECT);
}
private function getKeycloakClient(): KeycloakClient
{
return $this->clientRegistry->getClient('keycloak');
}
}
Functional tests are developped according to the official documentation:
public function testHomePage()
{
$client = static::createClient();
$session = $client->getContainer()->get('session');
$firewallName = 'main';
$token = new PostAuthenticationGuardToken(new User('foo', ['ROLE_USER']), $firewallName, $roles);
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
$client->request('GET', '/');
$this->assertTrue($client->getResponse()->isSuccessful());
}
Until now, tests pass, everything is fine.
But since I upgraded Symfony to 4.4, the method ControllerTrait::getUser() returns null and I'm facing the following error when running functional tests:
Error : Call to a member function getUsername() on null
I except to get the current user as usual when calling $this->getUser().
I tried to manually set the token in the security.token_storage but the error still persists.
I also tried to force authentication by removing the part "'127.0.0.1' == request.getClientIp() in the allow_if section of "security.yaml", and the response is now a 307 Temporary Redirect to the Keycloak service.
Did the behavior of $this->getUser() change between this 2 versions ?
Thank you for your help
I make authorization by ApiKey and I want to get 401 Unauthorized if no authorization data presented, and 403 Forbidden if authorization data is invalid. But I got 500 Internal Server Error in both situations.
security.yml:
security:
providers:
api_key_user_provider:
entity:
class: RestBundle:RestUser
property: apikey
firewalls:
rest_api_area:
pattern: ^/api
stateless: true
rest_auth:
header: x-apikey
provider: api_key_user_provider
access_control:
- { path: ^/api, roles: ROLE_REST_USER }
RestUserListener.php:
class RestUserListener implements ListenerInterface
{
protected $tokenStorage;
protected $authenticationManager;
private $header;
function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, $header)
{
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->header = $header;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$apikey = $request->headers->get($this->header);
if (!$apikey) return;
$token = new RestUserToken();
$token->setUser($apikey);
$authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($authToken);
return;
}
}
RestUserAuthenticationProvider.php:
class RestUserAuthenticationProvider implements AuthenticationProviderInterface
{
private $userProvider;
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if ($user)
{
$authenticatedToken = new RestUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException("Apikey not found.");
}
public function supports(TokenInterface $token)
{
return $token instanceof RestUserToken;
}
}
RestUserToken as simple as AbstractToken and has no additional logic.
api_key_user_provider is the standard entity provider identified by apikey property of RestUser
RestUserFactory also has no additional magic inside, just like in the official documentation
The RestUserListener::handle() method should handle the case of returning HTTP 401 or HTTP 403.
Simply having return; is not going to make this happen.
In a similar application I wrote, I did this:
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
//
...
//
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if ( /* something invalid here, so error */ ) {
$context = $request->getHost();
throw new UnauthorizedHttpException(
"Basic realm=\"$context\"",
'Please authenticate correctly or any other message here'
);
}
}
Throwing UnauthorizedHttpException will result in a HTTP 401 (you'll understand if you'll look at the source code of the exception).
For HTTP 403, you can use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException instead.
In a Symfony2 application using FOSUserBundle for user management, the user table has been filled through an import script from a csv file and the password generated from a combination of data.
I would like to force the user to change his password at the first login.
When the event FOSUserEvents::SECURITY_IMPLICIT_LOGIN occurs, redirect to the route fos_user_change_password if the field last_login is NULL.
My idea was rewriting the method onImplicitLogin(UserEvent $event) of the class AGI\UserBundle\EventListener\LastLoginListener like this but the class is not overwritten:
public function onImplicitLogin(UserEvent $event) {
$user = $event->getUser ();
if ($user->getLastLogin () === null) {
$user->setLastLogin ( new \DateTime () );
$this->userManager->updateUser ( $user );
$response = new RedirectResponse ( $this->router->generate ( 'fos_user_change_password' ) );
$this->session->getFlashBag ()->add ( 'notice', 'Please change your password' );
$event->setResponse ( $response );
}
}
I already have a bundle overwriting FOSUserBundle and it works for controllers, forms, etc but It looks like it is not the way to do it with eventListeners.
How can I force the user to change the password after the first login?
With the help of the precious hint from #sjagr about fos_user.security.implicit_login that drove me to fos_user.security.implicit_login and an external topic about doing stuff right after login, I got a working solution.
AGI\UserBundle\Resources\config\services.yml
login_listener:
class: 'AGI\UserBundle\EventListener\LoginListener'
arguments: ['#security.context', '#router', '#event_dispatcher']
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login', method: onSecurityInteractiveLogin }
AGI\UserBundle\EventListener\LoginListener.php
<?php
namespace AGI\UserBundle\EventListener;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class LoginListener {
private $securityContext;
private $router;
private $dispatcher;
public function __construct(SecurityContext $securityContext, Router $router, EventDispatcherInterface $dispatcher) {
$this->securityContext = $securityContext;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) {
if ($this->securityContext->isGranted ( 'IS_AUTHENTICATED_FULLY' )) {
$user = $event->getAuthenticationToken ()->getUser ();
if ($user->getLastLogin () === null) {
$this->dispatcher->addListener ( KernelEvents::RESPONSE, array (
$this,
'onKernelResponse'
) );
}
}
}
public function onKernelResponse(FilterResponseEvent $event) {
$response = new RedirectResponse ( $this->router->generate ( 'fos_user_change_password' ) );
$event->setResponse ( $response );
}
}
Thank you
If you require user change password due to some business rules, you can use kernel request EventListener:
<?php
namespace App\EventListener;
use App\Model\UserInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class ChangePasswordListener
{
private TokenStorageInterface $security;
private RouterInterface $router;
private array $excludedRoutes = [
'admin_change_password',
'admin_login',
'admin_login_check',
'admin_logout',
];
public function __construct(
TokenStorageInterface $security,
RouterInterface $router
) {
$this->security = $security;
$this->router = $router;
}
public function onKernelRequest(RequestEvent $event): void
{
if (false === $event->isMasterRequest()) {
return;
}
if ($event->getRequest()->isXmlHttpRequest()) {
return;
}
$currentRoute = $event->getRequest()->get('_route');
if (\in_array($currentRoute, $this->excludedRoutes, true)) {
return;
}
$token = $this->security->getToken();
if (null === $token) {
return;
}
$user = $token->getUser();
if ($user instanceof UserInterface && $user->shouldPasswordChange()) {
$response = new RedirectResponse($this->router->generate('admin_security_profile_change_password'));
$event->setResponse($response);
}
}
}
services.yaml:
services:
App\EventListener\ChangePasswordListener:
arguments:
- '#security.token_storage'
- '#router'
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: -100 }
You should provide also own UserInterface with method "shouldPasswordChange" and custom implementation of it.
It works great with Symfony 5.0 and PHP 7.4 but if you modify this code it should works also for lower PHP versions.
I'm using the basic user login/logout system provided with Symfony and it works fine as long as people log in. In that case the $user object is always provided as needed.
The problem is then when logged out (or not lgged in yet) there is no user object. Is there a possibility to have (in that case) a default user object provided with my own default values?
Thanks for your suggestions
Because the solution mention above by #Chopchop (thanks anyway for your effort) didn't work here I wrote a little workaround.
I created a new class called myController which extends Controller. The only function i override is the getUser() function. There I implement it like this:
public function getUser()
{
$user = Controller::getUser();
if ( !is_object($user) )
{
$user = new \ACME\myBundle\Entity\User();
$user->setUserLASTNAME ('RaRa');
$user->setID (0);
// etc...
}
return $user;
}
This works fine for me now. The only problem is that you really have to be careful NOT to forget to replace Controller by myController in all your *Controller.php files. So, better suggestions still welcome.
Works in Symfony 3.3
Using the suggestion of #Sfblaauw, I came up with a solution that uses a CompilerPass.
AppBundle/AppBundle.php
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideAnonymousUserCompilerPass());
}
}
OverrideAnonymousUserCompilerPass.php
class OverrideAnonymousCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('security.authentication.listener.anonymous');
$definition->setClass(AnonymousAuthenticationListener::class);
}
}
AnonymousAuthenticationListener.php
class AnonymousAuthenticationListener implements ListenerInterface
{
private $tokenStorage;
private $secret;
private $authenticationManager;
private $logger;
public function __construct(TokenStorageInterface $tokenStorage, $secret, LoggerInterface $logger = null, AuthenticationManagerInterface $authenticationManager = null)
{
$this->tokenStorage = $tokenStorage;
$this->secret = $secret;
$this->authenticationManager = $authenticationManager;
$this->logger = $logger;
}
public function handle(GetResponseEvent $event)
{
if (null !== $this->tokenStorage->getToken()) {
return;
}
try {
// This is the important line:
$token = new AnonymousToken($this->secret, new AnonymousUser(), array());
if (null !== $this->authenticationManager) {
$token = $this->authenticationManager->authenticate($token);
}
$this->tokenStorage->setToken($token);
if (null !== $this->logger) {
$this->logger->info('Populated the TokenStorage with an anonymous Token.');
}
} catch (AuthenticationException $failed) {
if (null !== $this->logger) {
$this->logger->info('Anonymous authentication failed.', array('exception' => $failed));
}
}
}
}
This file is a copy of the AnonymousAuthenticationListener that comes with Symfony, but with the AnonymousToken constructor changed to pass in an AnonymousUser class instead of a string. In my case, AnonymousUser is a class that extends my User object, but you can implement it however you like.
These changes mean that {{ app.user }} in Twig and UserInterface injections in PHP will always return a User: you can use isinstance to tell if it's an AnonymousUser, or add a method isLoggedIn to your User class which returns true in User but false in AnonymousUser.
you can redirect the user not authenticated and force a fake login (to create a user ANONYMOUS)
and set it as well on logout
public function logoutAction(){
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('VendorBundle:User')->findByUserName('annonymous');
$session = $this->getRequest()->getSession();
$session->set('user', $user);
}
and if user is not set
public function checkLoginAction(){
if(!$session->get('user')){
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('VendorBundle:User')->findByUserName('annonymous');
$session = $this->getRequest()->getSession();
$session->set('user', $user);
}
//this->redirect('/');
}
in you security.yml
security:
firewalls:
main:
access_denied_url: /check_login/
access_control:
- { path: ^/$, role: ROLE_USER }
This is only an example i haven't tested (and will probably don't, since i don't get the purpose of doing this:) )
Using Symfony 2.6
Like Gordon says use the authentication listener to override the default anonymous user.
Now you can add the properties that you need to the anonymous user, in my case the language and the currency.
security.yml
parameters:
security.authentication.listener.anonymous.class: AppBundle\Security\Http\Firewall\AnonymousAuthenticationListener
AnonymousAuthenticationListener.php
namespace AppBundle\Security\Http\Firewall;
...
use AppBundle\Security\User\AnonymousUser;
class AnonymousAuthenticationListener implements ListenerInterface
{
...
public function handle(GetResponseEvent $event)
{
...
try {
$token = new AnonymousToken($this->key, new AnonymousUser(), array());
...
}
}
}
AnonymousUser.php
class AnonymousUser implements UserInterface
{
public function getUsername() { return 'anon.'; }
}