I have setup my own login form in a security controller. to use the LoginForm I have configured this in the security configuration.
I want to use a custom login form authenticator to have more control over the authentication progress, register logins in the system,
and do anything I'd like to add (IP-check etc, etc...)
So there is also a LoginFormAuthenticator class in my application. somehow the authentication process doesn't even seem to use the methods of
the custom LoginFormAuthenticator. Is my security.yaml configured properly? how do I get all my configuration to work together?
The security in symfony seems so messy at some points, I can't begin to understand how people manage to properly configure it..
LoginFormAuthenticator:
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
/**
* Constructor
*
* #param Logger $logger
* #param LoginAttemptManagerInterface $loginAttemptManager
* #param LocationManagerInterface $locationManager
* #param RouterInterface $router
* #param UserPasswordEncoderInterface $userPasswordEncoder
* #param UserRepositoryInterface $userRepository
*/
public function __construct(Logger $logger, LoginAttemptManagerInterface $loginAttemptManager, LocationManagerInterface $locationManager, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder, UserRepositoryInterface $userRepository)
{
$this->_logger = $logger;
$this->_loginAttemptManager = $loginAttemptManager;
$this->_locationManager = $locationManager;
$this->_router = $router;
$this->_userPasswordEncoder = $userPasswordEncoder;
$this->_userRepository = $userRepository;
}
/**
* {#inheritdoc}
*/
protected function getLoginUrl()
{
return $this->_router->generate("login");
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
$credentials = $request->get("login_form");
return [
"username" => $credentials["username"],
"password" => $credentials["password"],
"token" => $credentials["_token"],
];
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials["username"];
try {
$user = $this->_userRepository->findOneByUsername($username);
if (null !== $user && $user instanceof UserInterface) {
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $this->_loginAttemptManager->create();
$user->addLoginAttempt($loginAttempt);
}
}
catch (NoResultException $e) {
return null;
}
catch (NonUniqueResultException $e) {
return null;
}
catch (UsernameNotFoundException $e) {
return null;
}
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
/* #var string $rawPassword the unencoded plain password */
$rawPassword = $credentials["password"];
if ($this->_userPasswordEncoder->isPasswordValid($user, $rawPassword)) {
return true;
}
return new CustomUserMessageAuthenticationException("Invalid credentials");
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
/* #var AbstractUser $user */
$user = $token->getUser();
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $user->getLastLoginAttempt();
$loginAttempt->success();
this->_loginAttemptManager->saveOne($loginAttempt, true);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// without this method the authentication process becomes a loop
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function supports(Request $request)
{
return $request->getPathInfo() != $this->getLoginUrl() || !$request->isMethod(Request::METHOD_POST);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
}
SecurityController:
class SecurityController extends AbstractController
{
/**
* #Route(path = "login", name = "login", methods = {"GET", "POST"})
* #Template(template = "security/login.html.twig")
*
* #param AuthenticationUtils $authUtils
* #param Request $request
* #return array
*/
public function login(AuthenticationUtils $authUtils, Request $request)
{
$form = $this->createLoginForm();
if (null !== $authUtils->getLastAuthenticationError()) {
$form->addError(new FormError(
$this->_translator->trans("error.authentication.incorrect-credentials", [], "security")
));
}
if (null != $authUtils->getLastUsername()) {
$form->setData([
"username" => $authUtils->getLastUsername(),
]);
}
// settings are in config/packages/security.yaml
// configuration authenticates user in login form authenticator service
return [
"backgroundImages" => $this->_backgroundImageManager->findAll(),
"form" => $form->createView(),
];
}
/**
* #return FormInterface
*/
private function createLoginForm() : FormInterface
{
$form = $this->createForm(LoginForm::class, null, [
"action" => $this->generateUrl("login"),
"method" => Request::METHOD_POST,
]);
$form->add("submit", SubmitType::class, [
"label" => $this->_translator->trans("btn.login", [], "button"),
"icon_name" => "sign-in",
"translation_domain" => false,
]);
return $form;
}
}
security.yaml:
security:
providers:
user_provider:
entity:
class: App\Entity\Model\AbstractUser
property: username
oauth_provider:
entity:
class: App\Entity\Model\ApiClient
property: name
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# The API-Oauth-Token-Firewall must be above the API-firewall
api_oauth_token:
pattern: ^/api/oauth/token$
security: false
# The API-firewall must be above the Main-firewall
api:
pattern: ^/api/*
security: true
stateless: true
oauth2: true
provider: oauth_provider
access_denied_handler: App\Service\Api\Security\ApiAccessDeniedHandler
main:
anonymous: true
guard:
authenticators:
- App\Service\Security\LoginFormAuthenticator
access_denied_handler: App\Service\Security\AccessDeniedHandler
provider: user_provider
form_login:
login_path: /login
check_path: /login
default_target_path: / #index
username_parameter: "login_form[username]"
password_parameter: "login_form[password]"
logout:
# the logout path overrides the implementation of the logout method
# in the security controller
path: /logout
target: / #index
remember_me:
secret: '%kernel.secret%'
lifetime: 43200 # 60 sec * 60 min * 12 hours
path: /
remember_me_parameter: "login_form[remember]"
encoders:
App\Entity\Model\AbstractUser:
algorithm: bcrypt
cost: 13
access_control:
# omitted from this question
role_hierarchy:
# omitted from this question
How did you come up with the logic of LoginFormAuthenticator::supports()?
shouldn't this be the opposite like:
return 'login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
Apparently I had two forms of authentication configured in security.yaml
So I've removed the form_login key from the config:
security.yaml
security:
providers:
user_provider:
entity:
class: App\Entity\Model\AbstractUser
property: username
oauth_provider:
entity:
class: App\Entity\Model\ApiClient
property: name
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# The API-Oauth-Token-Firewall must be above the API-firewall
api_oauth_token:
pattern: ^/api/oauth/token$
security: false
# The API-firewall must be above the Main-firewall
api:
pattern: ^/api/*
security: true
stateless: true
oauth2: true
provider: oauth_provider
access_denied_handler: App\Service\Api\Security\ApiAccessDeniedHandler
main:
anonymous: true
guard:
authenticators:
- App\Service\Security\LoginFormAuthenticator
access_denied_handler: App\Service\Security\AccessDeniedHandler
provider: user_provider
logout:
# the logout path overrides the implementation of the logout method
# in the security controller
path: /logout
target: / #index
remember_me:
secret: '%kernel.secret%'
lifetime: 43200 # 60 sec * 60 min * 12 hours
path: /
remember_me_parameter: "login_form[remember]"
And updated the LoginFormAuthenticator
- integrated
- also added checking CSRF token
LoginFormAuthenticator
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
const FORM = "login_form";
const USERNAME = "username";
const PASSWORD = "password";
const CSRF_TOKEN = "token";
/**
* Constructor
*
* #param CsrfTokenManagerInterface $csrfTokenManager
* #param Logger $logger
* #param LoginAttemptManagerInterface $loginAttemptManager
* #param LocationManagerInterface $locationManager
* #param RouterInterface $router
* #param UserPasswordEncoderInterface $userPasswordEncoder
* #param UserRepositoryInterface $userRepository
*/
public function __construct(CsrfTokenManagerInterface $csrfTokenManager, Logger $logger, LoginAttemptManagerInterface $loginAttemptManager, LocationManagerInterface $locationManager, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder, UserRepositoryInterface $userRepository)
{
$this->_csrfTokenManager = $csrfTokenManager;
$this->_logger = $logger;
$this->_loginAttemptManager = $loginAttemptManager;
$this->_locationManager = $locationManager;
$this->_router = $router;
$this->_userPasswordEncoder = $userPasswordEncoder;
$this->_userRepository = $userRepository;
}
/**
* Get Login URL
*
* #return string
*/
protected function getLoginUrl()
{
return $this->_router->generate("login");
}
/**
* Get Target URL
*
* #return string
*/
protected function getTargetUrl()
{
return $this->_router->generate("index");
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
$credentials = $request->request->get(self::FORM);
$request->getSession()->set(Security::LAST_USERNAME, $credentials["username"]);
return [
self::USERNAME => $credentials["username"],
self::PASSWORD => $credentials["password"],
self::CSRF_TOKEN => $credentials["_token"],
];
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials[self::USERNAME];
try {
$user = $this->_userRepository->findOneByUsername($username);
if (null !== $user && $user instanceof UserInterface) {
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $this->_loginAttemptManager->create();
$user->addLoginAttempt($loginAttempt);
}
return $user;
}
catch (NoResultException $e) {
throw new BadCredentialsException("Authentication failed");
}
catch (NonUniqueResultException $e) {
throw new BadCredentialsException("Authentication failed");
}
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$csrfToken = new CsrfToken(self::FORM, $credentials[self::CSRF_TOKEN]);
if (false === $this->_csrfTokenManager->isTokenValid($csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token');
}
/* #var string $rawPassword the unencoded plain password */
$rawPassword = $credentials[self::PASSWORD];
if ($this->_userPasswordEncoder->isPasswordValid($user, $rawPassword)) {
return true;
}
/* #var AbstractUser $user */
$loginAttempt = $user->getLastLoginAttempt();
if (null !== $loginAttempt) {
$this->_loginAttemptManager->saveOne($loginAttempt);
}
return new CustomUserMessageAuthenticationException("Invalid credentials");
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
/* #var AbstractUser $user */
$user = $token->getUser();
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $user->getLastLoginAttempt();
$loginAttempt->setStatus(LoginAttempt::STATUS_AUTHENTICATION_SUCCESS);
if (null !== $loginAttempt) {
$this->_loginAttemptManager->saveOne($loginAttempt);
}
return new RedirectResponse($this->getTargetUrl());
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception->getMessage());
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function supports(Request $request)
{
return $request->getPathInfo() === $this->getLoginUrl() && $request->isMethod(Request::METHOD_POST);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
}
Related
i need to add an extra field to the json login, currently i can POST a _username and _password to my login_check endpoint but i also need to send a _school_name so the same username can be used in different schools.
I am using the json_login (https://symfony.com/doc/current/security/json_login_setup.html) with the lexik jwt bundle. Should i create a custom controller for this or a GuardAuthenticator?
I tried extending the AbstractGuardAuthenticator and i tried the AbstractFormLoginAuthenticator but they are both not working for me. By default i used this:
login:
pattern: ^/api/v1/token
stateless: true
anonymous: true
user_checker: App\Security\UserChecker
json_login:
check_path: /api/v1/token/login
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
Then i added my custom Guard:
login:
pattern: ^/api/v1/token
stateless: true
anonymous: true
user_checker: App\Security\UserChecker
guard:
authenticators:
- App\Security\BaseAuthenticator
<?php
namespace App\Security;
use App\Entity\Client;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
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\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class BaseAuthenticator extends AbstractGuardAuthenticator
{
const LOGIN_ROUTE = 'login_check';
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private CsrfTokenManagerInterface $csrfTokenManager;
private UserPasswordEncoderInterface $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
/**
* #param Request $request
* #return bool
*/
public function supports(Request $request)
{
if ($request->attributes->get('_route') !== static::LOGIN_ROUTE) {
return false;
}
if (!$request->isMethod(Request::METHOD_POST)) {
return false;
}
return true;
}
/**
* #param Request $request
* #return mixed
*/
public function getCredentials(Request $request)
{
return [
'client_name' => $request->request->get('client_name'),
'username' => $request->request->get('username'),
'password' => $request->request->get('password')
];
}
/**
* #param mixed $credentials
* #param UserProviderInterface $userProvider
* #return UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$userRepository = $this->entityManager->getRepository(User::class);
$clientRepository = $this->entityManager->getRepository(Client::class);
$client = $clientRepository->findOneBy([
'name' => $credentials['client_name']
]);
if (!$client instanceof Client) {
throw new CustomUserMessageAuthenticationException('Client not found');
}
$user = $userRepository->findOneBy([
'client_id' => $client->getId(),
'username' => $credentials['username']
]);
if (!$user instanceof User) {
throw new CustomUserMessageAuthenticationException('User not found');
}
return $user;
}
/**
* #param mixed $credentials
* #param UserInterface $user
* #return bool
*/
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* #param Request $request
* #param TokenInterface $token
* #param string $providerKey
* #return Response|null
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
return null;
}
/**
* #param Request $request
* #param AuthenticationException|null $authException
* #return Response
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* #param Request $request
* #param AuthenticationException $exception
* #return JsonResponse
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* #return bool
*/
public function supportsRememberMe()
{
return false;
}
}
Thanks!
I'm implementing a login system with guard authentication in my symfony application. I've already started to implement the system but I must be doing something incorrectly.
I will start by showing what I have implemented and in the end explain what is happening ...
security.yml
security:
encoders:
UserBundle\Entity\User: bcrypt
providers:
custom_own_provider:
entity:
class: UserBundle:User
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
custom:
pattern: ^/ad/
anonymous: ~
provider: custom_own_provider
remember_me:
name: 'nothing'
secure: true
httponly: true
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
domain: ~
guard:
authenticators:
- app.authenticator.form
services.yml
services:
app.authenticator.form:
class: UserBundle\Security\LoginFormAuthenticator
autowire: true
arguments: ["#service_container"]
LoginController
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request) {
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'AppBundle:login:index.html.twig',
[
'error' => $error ? $error->getMessage() : NULL,
'last_username' => $lastUsername
]
);
}
Public controller with homepage: After login success, the user is redirected here. It happens that here, when validated if the user is authenticated I am unsuccessful. the 'getuser' from the security token is returning 'anon'.
/**
* #Route("/", name="homepage")
*/
public function publicHomepageAction (){
// DEBUG: This method gets a user from the Security Token Storage. The user here comes as 'anon'.
$user = $this->getUser();
// If user is already logged show internal homepage
$securityContext = $this->container->get('security.authorization_checker');
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')
|| $securityContext->isGranted('IS_AUTHENTICATED_FULLY')
){
// Code if user is authenticated
...
}
return $this->render('splash_page/homepage.html.twig');
}
And finally, my FormAuthenticator:
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
private $container;
/**
* Default message for authentication failure.
*
* #var string
*/
private $failMessage = 'Invalid credentials';
/**
* Creates a new instance of FormAuthenticator
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
return;
}
return array(
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
);
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
return $userProvider->loadUserByUsername($email);
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
return true;
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$url = $this->container->get('router')->generate('homepage');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* #param Request $request
*
* #return bool
*/
public function supports (Request $request){
return $request->request->has('_username') && $request->request->has('_password');
}
}
My User Entity implements UserInterface.
Although on the homepage it indicates to me that I am authenticated as 'anon', if I try to login again, through the debug, I noticed that it detects that I am authenticated and redirects to the homepage controller but in this controller it says that I am like anon and back to redirect to the splash page.
It gives the feeling that somewhere between validation and redirection to the controller loses token storage.
Any idea?
I am implementing a login system with guard in my symfony application.
I already setup the system, but i did one mistake, therefore i cant login...
The essentials:
My User Entity implements "AdvancedUserInterface, \Serializable" and it provides email property instead of username property..moreover i changed "getUsername"-function to:
public function getUsername()
{
return $this->email;
}
security.yml:
# app/config/security.yml
security:
providers:
main_db_provider:
entity:
class: AppBundle:User
property: email
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: /logout
target: /
guard:
authenticators:
- form_authenticator
provider: main_db_provider
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN }
services.yml:
services:
form_authenticator:
class: AppBundle\Security\FormAuthenticator
arguments: ["#service_container"]
login.html.twig:
<form action="{{ path('login') }}" method="POST">
<input type="text" name="email">
<input type="text" name="password">
<input type="submit" name="submit" value="Submit">
</form>
LoginController(part of it):
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request) {
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'AppBundle:login:index.html.twig',
[
'error' => $error ? $error->getMessage() : NULL,
'last_username' => $lastUsername
]
);
}
And last my FormAuthentificator:
class FormAuthenticator extends AbstractGuardAuthenticator
{
private $container;
/**
* Default message for authentication failure.
*
* #var string
*/
private $failMessage = 'Invalid credentials';
/**
* Creates a new instance of FormAuthenticator
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
return;
}
return array(
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
);
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
return $userProvider->loadUserByUsername($email);
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
return true;
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$url = $this->container->get('router')->generate('backend');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return false;
}
}
When i enter my valid credentials, i get:
Invalid credentials:
I also tried with other credentials, but always same error.
Anybody could help me to solve this issue?
Thanks and Greetings!
I am trying to set up switch_user functionality on an application which authenticates using Apache's auth_kerb. REMOTE_USER is returned correctly and am able to log in. However when I try to masquerade as a different user I am unable to. The user I wish to switch to does exist within the application. The attempt to switch user occurs but pre authentication is called again and the initial REMOTE_USER is loaded.
Any ideas on how to get switch_user working using remote_user and custom user provider?
security.yml
security:
providers:
webservice_user_provider:
id: webservice_user_provider
...
firewalls:
secured_area:
switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _masquerade }
pattern: ^/
remote_user:
provider: webservice_user_provider
...
services.yml
parameters:
account.security_listener.class: Acme\MyUserBundle\Listener\SecurityListener
services:
account.security_listener:
class: %account.security_listener.class%
arguments: ['#security.authorization_checker', '#session', '#doctrine.orm.entity_manager', '#router', '#event_dispatcher']
tags:
- { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
- { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }
- { name: kernel.event_listener, event: security.switch_user, method: onSecuritySwitchUser }
webservice_user_provider:
class: Acme\MyUserBundle\Security\User\WebserviceUserProvider
calls:
- [setEntityManager , ['#logger', '#doctrine.orm.entity_manager']]
...
SecurityListener.php
namespace Acme\MyUserBundle\Listener;
use ...
/**
* Class SecurityListener
* #package Acme\MyUserBundle\Listener
*/
class SecurityListener {
protected $session;
protected $security;
protected $em;
protected $router;
protected $dispatcher;
public function __construct(
AuthorizationCheckerInterface $security,
Session $session,
EntityManager $em,
UrlGeneratorInterface $router,
EventDispatcherInterface $dispatcher
// TraceableEventDispatcher $dispatcher
// ContainerAwareEventDispatcher $dispatcher
) {
$this->security = $security;
$this->session = $session;
$this->em = $em;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
/**
*
* #param AuthenticationFailureEvent $event
* #throws AuthenticationException
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event) {
throw new AuthenticationException($event->getAuthenticationException());
}
/**
* #param InteractiveLoginEvent $event
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) {
// set some defaults...
}
/**
* #param SwitchUserEvent $event
*/
public function onSecuritySwitchUser(SwitchUserEvent $event) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, array($this, 'onSwitchUserResponse'));
}
/**
* #param FilterResponseEvent $event
*/
public function onSwitchUserResponse(FilterResponseEvent $event) {
$response = new RedirectResponse($this->router->generate('acme_mybundle_default_index'));
$event->setResponse($response);
}
}
WebServiceProvider.php
namespace Acme\MyUserBundle\Security\User;
use ...
class WebserviceUserProvider implements UserProviderInterface {
protected $entityManager;
protected $logger;
/**
*
* #param LoggerInterface $logger
* #param EntityManager $em
*/
public function setEntityManager(LoggerInterface $logger, EntityManager $em) {
$this->logger = $logger;
$this->entityManager = $em;
}
/**
*
* #param string $username
* #return Person
* #throws UsernameNotFoundException
*/
public function loadUserByUsername($username) {
# Find the person
$person = $this->entityManager->getRepository('AcmeMyBundle:Person')
->find($username);
if ($person) {
$this->logger->debug("Logged in, finding person: " . $person->getUsername());
return $person;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
/**
*
* #param \Symfony\Component\Security\Core\User\UserInterface $person
* #throws \Symfony\Component\Security\Core\Exception\UnsupportedUserException
* #internal param \Symfony\Component\Security\Core\User\UserInterface $user
* #return Person
*/
public function refreshUser(UserInterface $person) {
if (!$person instanceof Person) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($person))
);
}
return $this->loadUserByUsername($person->getUsername());
}
/**
*
* #param type $class
* #return type
*/
public function supportsClass($class) {
return $class === 'Acme\MyBundle\Entity\Person';
}
}
This fix involves adapting AbstractPreAuthenticatedListener to check for the existence of the standard token that matches the logged in user, and if not a customised token that has stored the logged in user, but is attached to the 'switched to' userid.
This is the important (non copied) part of the code:
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getProviderKey() && $token->isAuthenticated() && $token->getUsername() === $user) {
return;
}
// Switch user token. Check the original token.
if ($token instanceof PreAuthenticatedSwitchUserToken && $this->providerKey == $token->getProviderKey() && $token->isAuthenticated() && $token->getOriginalUsername() === $user) {
return;
}
}
The token stores the logged in user and returns it with getOriginalUsername.
Store the existing authentication data (passed in $preAuthenticatedData)
/**
* Constructor.
*/
public function __construct($user, $credentials, $providerKey, array $roles = array(), $preAuthenticatedData)
{
parent::__construct($roles);
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->setUser($user);
$this->credentials = $credentials;
$this->providerKey = $providerKey;
if (!is_array($preAuthenticatedData) && count($preAuthenticatedData) > 0) {
throw new \InvalidArgumentException('No preauthenticated data. Must have the server login credentials.');
}
$this->original_username = $preAuthenticatedData[0];
if ($roles) {
$this->setAuthenticated(true);
}
}
Getter
public function getOriginalUsername() {
return $this->original_username;
}
Stash changes
/**
* {#inheritdoc}
*/
public function serialize()
{
return serialize(array($this->credentials, $this->providerKey, $this->original_username, parent::serialize()));
}
/**
* {#inheritdoc}
*/
public function unserialize($str)
{
list($this->credentials, $this->providerKey, $this->original_username, $parentStr) = unserialize($str);
parent::unserialize($parentStr);
}
These changes fit into the context of broader customisation of the Symfony security system. The source code for this is in github.
1 services.yml
Set account.security_listener, security.authentication.switchuser_listener and security.authentication.listener.remote_user_switch
This is in addition to the expected user provider.
2 security.yml
Use this security provider
secured_area:
switch_user: { role: ROLE_ALLOWED_TO_SWITCH, parameter: _masquerade }
pattern: ^/
remote_user_switch:
provider: webservice_user_provider
3 Check that the user provider loads the backing data for your user.
4 Install security files.
RemoteUserSwitchFactory.php: defines the listener to handle the
authentication events.
PreAuthenticatedWithSwitchUserListener.php:
our special authentication logic. SwitchUserListener.php: handles
the switch user event.
PreAuthenticatedSwitchUserToken.php: token to
store the logged in user as secondary data.
WebserviceUser.php: our
user data entity
WebserviceUserProvider.php: queries for user data.
I'm trying since 3 hours to install and configure FOSuser, which many developpers adviced me to use it.I wanted actually to make a normal login form without to use FOS but I had a lot of problems.I followed all steps in the documentation. the installation was ok , the configuration also but everytime when I try to log in , it shows "Bad credentials".So i find somehow this command that I executed :php app/console fos:user:create i give name-email-password. it work somehow but only with what i write, I mean when I register user in my registration form and try to log in it shows "Bad credentials".I hope that I was clear else please tell me what do you need to know
Here are my Users.php where i have all my users info to login...
namespace test\indexBundle\Document;
use FOS\UserBundle\Model\User as BaseUser;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
*
* #MongoDB\Document
*/
class Users extends BaseUser
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $userId;
/**
* #MongoDB\String
*/
protected $userEmail;
/**
* #MongoDB\String
*/
protected $userPassword;
/**
* #MongoDB\String
*/
protected $salt;
/**
* #MongoDB\Int
*/
protected $isActive;
public function __construct()
{
parent::__construct();
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
/**
* Set id
*
* #param id $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Get id
*
* #return id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set userId
*
* #param string $userId
*/
public function setUserId()
{
$this->userId = $this->salt;
}
/**
* Get userId
*
* #return string $userId
*/
public function getUserId()
{
return $this->userId;
}
/**
* Set userName
*
* #param string $userName
*/
public function setUserName($userName)
{
$this->userName = $userName;
}
/**
* Get userName
*
* #return string $userName
*/
public function getUserName()
{
return $this->username;
}
/**
* Set userEmail
*
* #param string $userEmail
*/
public function setUserEmail($userEmail)
{
$this->userEmail = $userEmail;
}
/**
* Get userEmail
*
* #return string $userEmail
*/
public function getUserEmail()
{
return $this->userEmail;
}
/**
* Set userPassword
*
* #param string $userPassword
*/
public function setPassword($userPassword)
{
$this->userPassword = $userPassword;
}
/**
* Get userPassword
*
* #return string $userPassword
*/
public function getPassword()
{
return $this->userPassword;
}
/**
* #inheritDoc
*/
public function getSalt()
{
return '';
}
/**
* #inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id
) = unserialize($serialized);
}
}
and here my security.yml:
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
test\indexBundle\Document\Users:
algorithm: sha1
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: /login_check
login_path: /login
provider: fos_userbundle
post_only: true
use_forward: false
username_parameter: email
password_parameter: password
failure_path: null
failure_forward: false
target_path_parameter: redirect_url
logout:
path: /logout
target: /blog
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
and login function:
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
if ($this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY'))
{
return $this->redirect($this->generateUrl('index_homepage'));
}
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR))
{
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
}
else
{
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('indexBundle:index:logIn.html.twig', array(
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
I might be wrong but I think FOSUserBundle requires a user to be activated after it's been created if you use the form registration, it's send out and email with a link I believe. I think you can use app/console fos:user:activate to activate if there is no email.