Using Symfony 4 with a security.yaml like this:
encoders:
App\Entity\User: sha256
providers:
public_users:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
form_login:
login_path: login
remember_me: true
remember_me:
secret: "%kernel.secret%"
name: relevea_remember_me
lifetime: 864000
always_remember_me: false
remember_me_parameter: user_login[stayConnected]
logout:
path: logout
target: /about
invalidate_session: false
access_control:
- { path: ^/auth, roles: IS_AUTHENTICATED_ANONYMOUSLY }
logoutoperation is not clearing the rememberMe token.
I can see that LogoutListener (https://github.com/symfony/security/blob/master/Http/Firewall/LogoutListener.php) is called after RememberMeListener (https://github.com/symfony/security/blob/master/Http/Firewall/RememberMeListener.php) so for LogoutListener, the token is null and nothing is cleared :/
The listeners list from TraceableFirewallListener:
Symfony\Component\Security\Http\Firewall\ChannelListener
Symfony\Component\Security\Http\Firewall\ContextListener
Symfony\Component\Security\Http\Firewall\LogoutListener
Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener
Symfony\Component\Security\Http\Firewall\RememberMeListener
Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener
Symfony\Component\Security\Http\Firewall\AccessListener
Why the logout listener is before others?
It looks a known issue since ... 2013!
https://github.com/symfony/symfony/issues/7104
So basically, you cant logout from a RememberMe token :/
You can override the firewall listener to call logout listener the last as below
security.firewall:
class: AppBundle\Security\FirewallListener
arguments:
- '#security.firewall.map'
- '#event_dispatcher'
- '#security.logout_url_generator'
tags:
- { name: kernel.event_subscriber }
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall;
use Symfony\Component\Security\Http\Firewall\LogoutListener;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
class FirewallListener extends Firewall
{
private $map;
private $exceptionListeners;
private $logoutUrlGenerator;
private $dispatcher;
public function __construct(FirewallMapInterface $map, EventDispatcherInterface $dispatcher, LogoutUrlGenerator $logoutUrlGenerator)
{
$this->map = $map;
$this->dispatcher = $dispatcher;
$this->exceptionListeners = new \SplObjectStorage();
$this->logoutUrlGenerator = $logoutUrlGenerator;
parent::__construct($map, $dispatcher);
}
/**
* {#inheritdoc}
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
if ($this->map instanceof FirewallMap && $config = $this->map->getFirewallConfig($event->getRequest())) {
$this->logoutUrlGenerator->setCurrentFirewall($config->getName(), $config->getContext());
}
// register listeners for this firewall
list($listeners, $exceptionListener) = $this->map->getListeners($event->getRequest());
if (null !== $exceptionListener) {
$this->exceptionListeners[$event->getRequest()] = $exceptionListener;
$exceptionListener->register($this->dispatcher);
}
// initiate the listener chain
$logoutListener = null;
foreach ($listeners as $listener) {
if ($listener instanceof LogoutListener) {
$logoutListener = $listener;
continue;
}
$listener->handle($event);
if ($event->hasResponse()) {
break;
}
}
if ($logoutListener) {
$logoutListener->handle($event);
}
}
/**
* {#inheritdoc}
*/
public function onKernelFinishRequest(FinishRequestEvent $event)
{
if ($event->isMasterRequest()) {
$this->logoutUrlGenerator->setCurrentFirewall(null);
}
parent::onKernelFinishRequest($event);
}
}
Related
With the new Symfony 5.1 security system, I'm not able to fire the InteractiveLoginEvent.
I followed the configuration in the official documentation (here and here) and the system was working perfectly in the former security system.
Below is my security.yaml :
security:
enable_authenticator_manager: true
encoders:
App\Entity\User:
algorithm: auto
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
form_login:
login_path: login
check_path: login
entry_point: form_login
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: logout
And the UserLocalSuscriber :
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($user->getLocale() !== null) {
$this->session->set('_locale', $user->getLocale());
}
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
];
}
}
How to configure it correctly? Without it, the user locale is not set properly once the user has been logged in.
For the Symfony's new security system, the SecurityEvents::INTERACTIVE_LOGIN has been replaced with Symfony\Component\Security\Http\Event\LoginSuccessEvent.
Change your subscriber to listen for this one:
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class UserLocaleSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => 'onLoginSuccess',
];
}
public function onLoginSuccess(LoginSuccessEvent $event): void
{
//...
}
}
These new events are mentioned briefly in the blog with the announcement for the new authenticator system.
The rest of the documentation has not been updated yet, the new auth system will probably become the default on a future Symfony realese, but right now it still is an experimental feature.
Try using this method onSecurityInteractivelogin()
I have a Symfony 3.3.13 system with various forms.
To achieve "deep-linking" in these forms, ie. being able to click on an email link, login and then be redirected to the form I have added the following changes:
config.yml
framework:
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
...
more
...
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
form_login: <--this line alone breaks CSRF
use_referer: true <--I tried partial combinations, none seems to make CSRF work
login_path: /security/login
use_forward: true
success_handler: login_handler
csrf_token_generator: security.csrf.token_manager <--added based on answer, doesn't help
src/AppBundle/Resources/config/services.yml
login_handler:
class: AppBundle\Service\LoginHandler
arguments: ['#router', '#doctrine.orm.entity_manager', '#service_container']
src/AppBundle/Service/Loginhandler.php
<?php
/**
* Created by PhpStorm.
* User: jochen
* Date: 11/12/17
* Time: 12:31 PM
*/
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Routing\RouterInterface;
use Doctrine\ORM\EntityManager;
class LoginHandler implements AuthenticationSuccessHandlerInterface
{
private $router;
private $container;
private static $key;
public function __construct(RouterInterface $router, EntityManager $em, $container) {
self::$key = '_security.main.target_path';
$this->router = $router;
$this->em = $em;
$this->session = $container->get('session');
}
public function onAuthenticationSuccess( Request $request, TokenInterface $token ) {
//check if the referer session key has been set
if ($this->session->has( self::$key )) {
//set the url based on the link they were trying to access before being authenticated
$route = $this->session->get( self::$key );
//remove the session key
$this->session->remove( self::$key );
//if the referer key was never set, redirect to a default route
return new RedirectResponse($route);
} else{
$url = $this->generateUrl('portal_job_index');
return new RedirectResponse($url);
}
}
}
I have also made sure that csrf is enabled on the login form like this:
src/AppBundle/resources/views/security/login.html.twig
<form action="{{ path('app_security_login') }}" method="post" autocomplete="off">
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
app/config/services.yml
app.legacy_token_authenticator:
class: AppBundle\Security\LegacyTokenAuthenticator
arguments: ["#router", "#session", "%kernel.environment%", "#security.csrf.token_manager"]
src/AppBundle/Security\legacyTokenAuthenticator
class LegacyTokenAuthenticator extends AbstractGuardAuthenticator
{
private $session;
private $router;
private $csrfTokenManager;
public function __construct(
RouterInterface $router,
SessionInterface $session,
$environment,
CsrfTokenManagerInterface $csrfTokenManager
) {
if ($environment != 'test'){
session_start();
}
$session->start();
$this->setSession($session);
$this->csrfTokenManager = $csrfTokenManager;
$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)
{
$csrfToken = $request->request->get('_csrf_token');
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
$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;
}
}
All CSRF checks - including on the login form - fail always when I add the 5 lines in security.yml starting with form_login. The error I get is:
The CSRF token is invalid. Please try to resubmit the form. portalbundle_portal_job
Caused by:
When I remove these 5 lines, all CSRF tokens work.
Here is a security.yml file I have from one of my projects which has csrf protection enabled. I do use the FOS UserBundle, which looks to be different from yours, but you might be able to see something here that helps. Specifically, a csrf generator has to be specified to use FOS UserBundle (under firewalls: main: form_login). I also have access_control patterns setup so that some endpoints are only accessible if a user is authenticated with a specific role -- but I don't think this will affect csrf. See below:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/event, role: ROLE_USER }
Also in my main config.yml I've enabled csrf under framework. Here's a snip of the whole thing:
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
For me, manually dealing with Symfony CSRF tokens is a major headcache. Short of doing it just to learn how to do it, I think there is almost always a simpler solution.
My solution for CSRF protection in a login does not run into this issue.
I create the form login using the Form component.
function loginAction()
{
$login = $this->createForm(LoginType::class);
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
return $this->render('Path/to/login.html.twig', [
'form' => $login->createView(),
'error' => $error,
]);
}
LoginType.php:
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
//...
class LoginType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', TextType::class)
->add('password', PasswordType::class)
;
}
}
login.html.twig:
{# template information #}
{{ form_start(form) }}
{{ form_row(form.username, {
'full_name': '_username'
} ) }}
{{ form_row(form.password, {
'full_name': '_password'
} ) }}
{{ form_end(form) }}
{# template information #}
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: /
form_login:
use_referer: true
login_path: /security/login
success_handler: login_handler
always_use_default_target_path: false
default_target_path: /
If you have CSRF enabled on forms, then your login form will be CSRF protected without any custom Guard authenticator.
[Symfony\Component\Debug\Exception\ContextErrorException]
Catchable Fatal Error: Argument 2 passed to Symfony\Component\Security\Core\Authentication\Provider\SimpleAuthenticationProvider::__construct() must implement interface Symfony\Component\Security\Core\User\UserProviderInterface, instance of Delivve\WebBundle\Service\WebKeyUsersService given, called in /home/delivve-webservice/app/cache/de_/ap_DevDebugProjectContainer.php on line 4611 and defined
What happens is that I have an api that works, but now I need to make the web service log face or google account, but this error of the above, follow this tutorial to make
http://nyrodev.info/fr/posts/286/Connexions-OAuth-Multiple-avec-Symfony-2-3
And apena in OAuthMembersService.php file includes the useSymfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; because symfony complained of is not having such imports.
I'm really doubt
I implemented the following classes:
<?php
namespace Delivve\WebBundle\Security;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser as BaseOAuthUser;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
class WebKeyUserProvider extends BaseOAuthUser {
protected $data;
public function __construct(UserResponseInterface $response) {
parent::__construct($response->getUsername());
$this->data = array(
'provider'=>$response->getResourceOwner()->getName(),
'providerId'=>$response->getUsername()
);
$vars = array(
'nickname',
'realname',
'email',
'profilePicture',
'accessToken',
'refreshToken',
'tokenSecret',
'expiresIn',
);
foreach($vars as $v) {
$fct = 'get'.ucfirst($v);
$this->data[$v] = $response->$fct();
}
}
public function getData() {
return $this->data;
}
/**
* {#inheritDoc}
*/
public function getRoles() {
return array('ROLE_OAUTH_USER');
}
}
<?php
namespace Delivve\WebBundle\Service;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use Delivve\WebBundle\Security\WebKeyUserProvider;
class WebKeyUsersService implements UserProviderFactoryInterface, OAuthAwareUserProviderInterface {
public function loadUserByUsername($username) {
throw new Exception('loadByUsername not implemented');
}
public function supportsClass($class) {
return $class === "Delivve\\WebBundle\\Security\\WebKeyUserProvider";
}
public function refreshUser(\Symfony\Component\Security\Core\User\UserInterface $user) {
if (!$this->supportsClass(get_class($user))) {
throw new UnsupportedUserException(sprintf('Unsupported user class "%s"', get_class($user)));
}
return $user;
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response) {
return new OAuthUser($response);
}
public function create(ContainerBuilder $container, $id, $config)
{
// TODO: Implement create() method.
}
public function getKey()
{
// TODO: Implement getKey() method.
}
public function addConfiguration(NodeDefinition $builder)
{
// TODO: Implement addConfiguration() method.
}
}
And these are my configurations:
routingSecurityOAuth.yml
hwi_oauth_login:
resource: "#HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /login
hwi_oauth_redirect:
resource: "#HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
facebook_login:
pattern: /login/check-facebook
google_login:
pattern: /login/check-google
web_target:
pattern: /target
defaults: { _controller: DelivveWebBundle:Security:oauthTarget }
service
services:
web_key_user_provider:
class: Delivve\WebBundle\Service\WebKeyUsersService
security
security:
providers:
web_key_user_provider:
id: web_key_user_provider
firewalls:
web_key:
pattern: ^/web/*
anonymous: ~
provider: web_key_user_provider
oauth:
resource_owners:
facebook: "/web/login/check-facebook"
google: "/web/login/check-google"
# linkedin: "/web/login/check-linkedin"
login_path: /web/login
failure_path: /web/login
check_path: /web/login_check
default_target_path: /web/target
oauth_user_provider:
service: web_key_user_provider
default:
anonymous: ~
access_control:
- { path: ˆ/web/target, roles: ROLE_OAUTH_USER }
- { path: ˆ/web/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
routing
web_key_register:
pattern: /webRegister
defaults: { _controller: DelivveWebBundle:Security:webRegister }
web_key:
resource: "#DelivveWebBundle/Resources/config/routingSecurityOAuth.yml"
prefix: /web/
config
hwi_oauth:
firewall_name: web_key
resource_owners:
facebook:
type: facebook
client_id: %facebook_client_id%
client_secret: %facebook_client_secret%
scope: email
infos_url: "https://graph.facebook.com/me?fields=username,name,email,picture.type(large)"
paths:
email: email
profilepicture: picture.data.url
options:
display: popup
google:
type: google
client_id: %google_client_id%
client_secret: %google_client_secret%
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
paths:
email: email
profilepicture: picture
I use FriendsOfSymfony UserBundle. It works when I set everything in firewall as form_login, but if I set it to simple_form to use Custom Authenticator then it let's me login even if the account is locked or disabled. I want to check if the user comes from the correct IP, that's why I created the custom authenticator, but it seems that some authentication coming from FOS is not processed this way. How can I use simple_form with custom authenticator while still keeping full functionality of FOS UserBundle?
Is there some other way I can achieve some other authentication than just the standard? Maybe I'm doing something wrong? I know I can correct this code of my authenticator to check for locked/enabled etc, but I figured - since it's actually already done in FOS - why should I?
EDIT: Also, I noticed that when I use simple_form the methods of class Symfony\Component\Security\Core\User\UserChecker aren't being called.
Below is my code of authenticator and security.yml:
config.yml
services:
login_authenticator:
class: Forex\AlchemyBundle\Security\LoginAuthenticator
arguments: ["#security.encoder_factory"]
security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
main:
pattern: ^/
simple_form:
authenticator: login_authenticator
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout: true
anonymous: true
access_control:
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } # To be removed
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/.*, roles: ROLE_USER }
LoginAuthenticator
<?php
namespace Forex\AlchemyBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class LoginAuthenticator implements SimpleFormAuthenticatorInterface
{
private $encoderFactory;
public function __construct(EncoderFactoryInterface $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
try {
$user = $userProvider->loadUserByUsername($token->getUsername());
} catch (UsernameNotFoundException $e) {
throw new AuthenticationException('Invalid username or password');
}
$encoder = $this->encoderFactory->getEncoder($user);
$passwordValid = $encoder->isPasswordValid(
$user->getPassword(),
$token->getCredentials(),
$user->getSalt()
);
if ($passwordValid) {
$request = Request::createFromGlobals();
$current_ip = $request->server->get('REMOTE_ADDR');
$user->setLoggedIP($current_ip);
if (!$user->isValidIP()) {
throw new AuthenticationException(
"You cannot login from your location.",
100
);
}
return new UsernamePasswordToken(
$user,
$user->getPassword(),
$providerKey,
$user->getRoles()
);
} else {
// TODO: Check if there weren't too many tries to login
}
throw new AuthenticationException('Invalid username or password');
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof UsernamePasswordToken
&& $token->getProviderKey() === $providerKey;
}
public function createToken(Request $request, $username, $password, $providerKey)
{
return new UsernamePasswordToken($username, $password, $providerKey);
}
}
I was facing the same issue. Here is how I solved it.
You must call checkPreAuth() and checkPostAuth() methods of your UserChecker class from within the authenticateToken() method in your Authenticator.
That is done this way:
1) Configure the user_checker service:
services:
app.user_checker:
class: AppBundle\Security\UserChecker
2) Configure the autenticator as a service and inject the user_checker service:
services:
app.my_authenticator:
class: AppBundle\Security\MyAuthenticator
arguments: ["#app.user_checker", "#security.password_encoder"]
3) Now you can call checkPreAuth() and checkPostAuth() on authenticateToken()
Anyway I think symfony approach is the right one, because in my case y needed to perform different checks in simple_form than in login_form.
I'm actually trying to create a login form matching with my database.
The form works well but I've a problem using a UserRepository. Symfony gives me the following error:
The user provider must return a UserInterface object.
exception 'Symfony\Component\Security\Core\Exception\AuthenticationServiceException' with message 'The user provider must return a UserInterface object.' in C:\wamp\www\php\Promocast\Symfony\vendor\symfony\src\Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider.php:101
But as you can see below, my user entity is implemented by UserInterface so I really don't understand what's the mistake here.
My user entity:
<?php
namespace Promocast\UtilisateurBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* Promocast\UtilisateurBundle\Entity\ImUser
*
* #ORM\Entity(repositoryClass="Promocast\UtilisateurBundle\Entity\ImUserRepository")
*/
class ImUser implements UserInterface
{
/**
* Here all my var and getter/setter
* ...
*/
/**
* Fonctions UserInterface
*
*/
public function eraseCredentials()
{
}
public function getRoles()
{
return $this->idrole;
}
public function equals(UserInterface $user)
{
return $user->getUsername() === $this->login;
}
public function getUsername()
{
return $this->login;
}
public function getSalt()
{
return '';
}
}
My user repository:
<?php
namespace Promocast\UtilisateurBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
class ImUserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($login)
{
$user = $this->findBy(array("login" => $login));
if (!$user) {
throw new UsernameNotFoundException(sprintf('No user with name "%s" was found.', $login));
}
return $user;
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Promocast\UtilisateurBundle\Entity\ImUser';
}
}
And my security.yml:
security:
encoders:
Promocast\UtilisateurBundle\Entity\ImUser: plaintext
#Promocast\UtilisateurBundle\Security\Provider\LoginWebService: plaintext
role_hierarchy:
ROLE_USER_SUP: ROLE_USER
ROLE_ADMIN: ROLE_USER_SUP
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_USER_SUP, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
main:
entity: { class: PromocastUtilisateurBundle:ImUser }
#entity: { class: Promocast\UtilisateurBundle\Entity\ImUser, property: login }
#id: webservice_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/(login$|register|resetting)
anonymous: true
main:
pattern: ^/
form_login:
login_path: /login
check_path: /login_check
username_parameter: _login
password_parameter: _password
remember_me:
key: %secret%
anonymous: true
provider: main
logout: true
logout:
path: /logout
target: /
access_control:
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
#- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
#- { path: /.*, role: ROLE_USER }
#- { path: /login, role: IS_AUTHENTICATED_ANONYMOUSLY }
Thanks a lot!
It looks like your problem is here:
public function loadUserByUsername($login)
{
$user = $this->findBy(array("login" => $login));
if (!$user) {
throw new UsernameNotFoundException(sprintf('No user with name "%s" was found.', $login));
}
return $user;
}
$this->findBy() is probably returning a cursor/resultset rather than one row as you intended (even if the resultset contains only one row).
Try just using $this->findOneBy() instead.
Based on your code, it looks like you are following the cookbook tutorial on How to load Security Users from the Database (the Entity Provider) - but if you're not, that is a helpful resource in this case.
Caveat: I'm far from an expert on the S2 security system. I'm just posting what works for me.
There is nothing in your config file that tells the system to use ImUserRepository as a user provider. So the system is basically trying to use a default provider. Furthermore, using a repository is problematical at best. Doctrine repos can only come from Doctrine entity managers so you asking the system to say hey my user class is a doctrine entity therefore I should use a repository and I guess I'll use this entity manager. Not going to happen.
Make your user provider a service and then inject the entity manager into it.
security:
providers:
my_provider:
id: zayso_core.user.provider
<service id="zayso_core.user.provider" class="Zayso\CoreBundle\Component\User\UserProvider" public="true">
<argument type="service" id="doctrine.orm.accounts_entity_manager" />
<argument type="string">%zayso_core.user.class%</argument>
</service>
You will probably also want to encode your passwords eventually so:
security:
encoders:
Zayso\AreaBundle\Component\User\User:
id: zayso_core.user.encoder
<service id="zayso_core.user.encoder" class="Zayso\CoreBundle\Component\User\Encoder" public="true">
</service>
And that should get you going.