Symfony authenticate user with external api key - api

I am trying to authenticate a user via external api key request following this http://symfony.com/doc/current/cookbook/security/api_key_authentication.html#cookbook-security-api-key-config
What is ["#your_api_key_user_provider"] ?
If I put something like ["test"] I get an error.
[UPDATE]
This is my ApiKeyAuthenticator.php:
// src/Acme/HelloBundle/Security/ApiKeyAuthenticator.php
namespace Acme\HelloBundle\Security;
use ////
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
protected $userProvider;
public function __construct(ApiKeyUserProvider $userProvider)
{
$this->userProvider = $userProvider;
}
public function createToken(Request $request, $providerKey)
{
if (!$request->query->has('apikey')) {
throw new BadCredentialsException('No API key found');
}
return new PreAuthenticatedToken(
'anon.',
$request->query->get('apikey'),
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$apiKey = $token->getCredentials();
$username = $this->userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
throw new AuthenticationException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $this->userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
}
While the user provider is this:
// src/Acme/HelloBundle/Security/ApiKeyUserProvider.php
namespace Acme\HelloBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_USER')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return 'Symfony\Component\Security\Core\User\User' === $class;
}
}
The service should be just this:
services:
# ...
apikey_authenticator:
class: Acme\SeedBundle\Security\ApiKeyAuthenticator
arguments: ["#ApiKeyUserProvider"]
But i got this error: The service "apikey_authenticator" has a dependency on a non-existent service "apikeyuserprovider".
Thanks

That is the user provider service that you should have created following this doc:
http://symfony.com/doc/current/cookbook/security/custom_provider.html
So you register your user provider as a service IE: apikey_userprovider
http://symfony.com/doc/current/cookbook/security/custom_provider.html#create-a-service-for-the-user-provider
Then pass it using ["#apikey_userprovider"]
So your Services File should look like:
parameters:
apikey_userprovider.class: Acme\HelloBundle\Security\ApiKeyUserProvider
apikey_authenticator.class: Acme\SeedBundle\Security\ApiKeyAuthenticator
services:
apikey_userprovider:
class: %apikey_userprovider.class%
apikey_authenticator:
class: %apikey_authenticator.class%
arguments: ["#apikey_userprovider"]
You need to define your user provider as a service. This is what the # operator is telling symfony to look for. Defining your classes in the parameters is just part of Symfony Coding Standards

Related

Symfony 4: Fetching user from DB ind custom user provider returns "Bad Credentials"

I am building an API based on Symfony 4.
In my custom user provider I dump the users email and the user data from database.
The email is shown but the second dump does not appear.
While fetching the user data it returns "Bad Credentials".
Here is my user provider:
<?php
// src/Security/User/WebserviceUserProvider.php
namespace App\Security\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class WebserviceUserProvider implements UserProviderInterface
{
private $doctrine;
public function __construct (\Doctrine\Bundle\DoctrineBundle\Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function loadUserByUsername($email)
{
var_dump($email);
$userData = $this->doctrine->getManager()
->createQueryBuilder('SELECT u FROM users u WHERE u.email = :email')
->setParameter('email', $email)
->getQuery()
->getOneOrNullResult();
var_dump($userData);exit;
// pretend it returns an array on success, false if there is no user
if ($userData) {
$username = $userData['email'];
$password = $userData['password'];
$salt = $userData['salt'];
$roles = $userData['roles'];
// ...
return new WebserviceUser($username, $password, $salt, $roles);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return WebserviceUser::class === $class;
}
}
If I send my json login data it returns the following:
string(13) "test#test.com" {"code":401,"message":"Bad credentials"}
Does anyone know this problem?

Symfony 3.2: Authenticate users through different properties

I want to achieve the following:
In my installation there are two bundles, ApiBundle and BackendBundle. Users are defined in BackendBundle, though I could put them in a UserBundle later.
ApiBundle basically provides a controller with api methods like for example getSomething().
BackendBundle has the user entities, services and some views like a login form and a backend view. From the backend controller I would want to access certain api methods.
Other api methods will be requested from outside. Api methods will be requested through curl.
I would want to have different users for both purposes. The User class implements UserInterface and has properties like $username, $password and $apiKey.
Now basically I want to provide an authentication method through login form with username and password, and another authentication method for api calls through curl from outside, that only will require the apiKey.
In both cases, the authenticated user then should have access to different ressources.
My security.yml so far looks like this:
providers:
chain_provider:
chain:
providers: [db_username, db_apikey]
db_username:
entity:
class: BackendBundle:User
property: username
db_apikey:
entity:
class: BackendBundle:User
property: apiKey
encoders:
BackendBundle\Entity\User:
algorithm: bcrypt
cost: 12
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
form_login:
login_path: login
check_path: login
default_target_path: backend
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /login
provider: chain_provider
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: ROLE_API }
- { path: ^/backend, roles: ROLE_BACKEND }
Question 1: How can I achieve that users from the same entity can authenticate differently and access certain ressources? The desired behaviour is authentication with username/password OR only apikey.
Question 2: How can I achieve, that api methods return a json if the requester is not authenticated properly, instead of returning the view for the login form? Eg. I want to return something like { 'error': 'No access' } instead of the html for the login form if someone requests /api/getSomething and of course I want to show the login form if someone requests /backend/someroute.
Every help is very much appreciated! :)
The symfony docs say:
The main job of a firewall is to configure how your users will authenticate. Will they use a login form? HTTP basic authentication? An API token? All of the above?
I think my question basically is, how can I have login form AND api token authentication at the same time.
So maybe, I need something like this: http://symfony.com/doc/current/security/guard_authentication.html#frequently-asked-questions
Question 1: When you want to authenticate users by apiKey only, then best possible solution would be implement own User provider. The solution is well decribed in the Symfony doc: http://symfony.com/doc/current/security/api_key_authentication.html
EDIT - You can have as many user providers as you want and if one fails, then another becomes to play - described here https://symfony.com/doc/current/security/multiple_user_providers.html
Down below is code for ApiKeyAuthenticator which gets the token and calls ApiKeyUserProvider to find/get user for it. In case user is found, than is provided to Symfony security. ApiKeyUserProvider needs UserRepository to user operations - I'm sure you have one, otherwise write it.
Code isn't tested, so little bit of tweaking may be necessary.
So lets get to work:
src/BackendBundle/Security/ApiKeyAuthenticator.php
namespace BackendBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
protected $httpUtils;
public function __construct(HttpUtils $httpUtils)
{
$this->httpUtils = $httpUtils;
}
public function createToken(Request $request, $providerKey)
{
//use this only if you want to limit apiKey authentication only for certain url
//$targetUrl = '/login/check';
//if (!$this->httpUtils->checkRequestPath($request, $targetUrl)) {
// return;
//}
// get an apikey from authentication request
$apiKey = $request->query->get('apikey');
// or if you want to use an "apikey" header, then do something like this:
// $apiKey = $request->headers->get('apikey');
if (!$apiKey) {
//You can return null just skip the authentication, so Symfony
// can fallback to another authentication method, if any.
return null;
//or you can return BadCredentialsException to fail the authentication
//throw new BadCredentialsException();
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof ApiKeyUserProvider) {
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
get_class($userProvider)
)
);
}
$apiKey = $token->getCredentials();
$username = $userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
throw new CustomUserMessageAuthenticationException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// this contains information about *why* authentication failed
// use it, or return your own message
return new JsonResponse(//$exception, 401);
}
}
src/BackendBundle/Security/ApiKeyUserProvider.php
namespace BackendBundle\Security;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use BackendBundle\Entity\User;
use BackendBundle\Entity\UserORMRepository;
class ApiKeyUserProvider implements UserProviderInterface
{
private $userRepository;
public function __construct(UserORMRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function getUsernameForApiKey($apiKey)
{
//use repository method for getting user from DB by API key
$user = $this->userRepository->...
if (!$user) {
throw new UsernameNotFoundException('User with provided apikey does not exist.');
}
return $username;
}
public function loadUserByUsername($username)
{
//use repository method for getting user from DB by username
$user = $this->userRepository->...
if (!$user) {
throw new UsernameNotFoundException(sprintf('User "%s" does not exist.', $username));
}
return $user;
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Expected an instance of ..., but got "%s".', get_class($user)));
}
if (!$this->supportsClass(get_class($user))) {
throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userRepository->getClassName(), get_class($user)));
}
//use repository method for getting user from DB by ID
if (null === $reloadedUser = $this->userRepository->findUserById($user->getId())) {
throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
}
return $reloadedUser;
}
public function supportsClass($class)
{
$userClass = $this->userRepository->getClassName();
return ($userClass === $class || is_subclass_of($class, $userClass));
}
}
Services definition:
services:
api_key_user_provider:
class: BackendBundle\Security\ApiKeyUserProvider
apikey_authenticator:
class: BackendBundle\Security\ApiKeyAuthenticator
arguments: ["#security.http_utils"]
public: false
And finally security provider config:
providers:
chain_provider:
chain:
providers: [api_key_user_provider, db_username]
api_key_user_provider:
id: api_key_user_provider
db_username:
entity:
class: BackendBundle:User
property: username
I encourage you to study Symfony docs more, there is very good explanation for the authentication process, User entities, User providers, etc.
Question 2: You can achieve different response types for access denied event by defining own Access denied handler:
namespace BackendBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
public function handle(Request $request, AccessDeniedException $accessDeniedException)
{
$route = $request->get('_route');
if ($route == 'api')) {
return new JsonResponse($content, 403);
} elseif ($route == 'backend')) {
return new Response($content, 403);
} else {
return new Response(null, 403);
}
}
}

Load a listener before the security or custom provider

I'm working on a custom provider that works exactly like a classical user form, however I have to give a second parameter to identify the user: a websiteId (I'm creating a dynamic website plateform).
So a username is no more unique, but the combinaison of username and websiteId it is.
I successfully created my custom authentication, the last problem I have is to get the websiteId from the domain thanks to a listener, it works, but infortunately the method that get the website id from the domain is loaded after my authentication provider, so I can't get the websiteId in time :(
I tried to change the listener priority (test 9999, 1024, 255 and 0, and negative numbers -9999, -1024, -255 etc...), in vain, it's loaded always after.
Here my code:
services.yml:
services:
# Listeners _________________
website_listener:
class: Sybio\Bundle\WebsiteBundle\Services\Listener\WebsiteListener
arguments:
- #doctrine
- #sybio.website_manager
- #translator
- %sybio.states%
tags:
- { name: kernel.event_listener, event: kernel.request, method: onDomainParse, priority: 255 }
# Security _________________
sybio_website.user_provider:
class: Sybio\Bundle\WebsiteBundle\Security\Authentication\Provider\WebsiteUserProvider
arguments: [#website_listener, #doctrine.orm.entity_manager]
My listener is "website_listener", and you can see i use it for my sybio_website.user_provider as argument.
WebsiteListener:
// ...
class WebsiteListener extends Controller
{
protected $doctrine;
protected $websiteManager;
protected $translator;
protected $websiteId;
/**
* #var array
*/
protected $entityStates;
public function __construct($doctrine, $websiteManager, $translator, $entityStates)
{
$this->doctrine = $doctrine;
$this->websiteManager = $websiteManager;
$this->translator = $translator;
$this->entityStates = $entityStates;
}
/**
* #param Event $event
*/
public function onDomainParse(Event $event)
{
$request = $event->getRequest();
$website = $this->websiteManager->findOne(array(
'domain' => $request->getHost(),
'state' => $this->entityStates['website']['activated'],
));
if (!$website) {
throw $this->createNotFoundException($this->translator->trans('page.not.found'));
}
$this->websiteId = $website->getId();
}
/**
* #param integer $websiteId
*/
public function getWebsiteId()
{
return $this->websiteId;
}
}
$websiteId is hydrated, not in time as you will see in my provider...
WebsiteUserProvider:
<?php
namespace Sybio\Bundle\WebsiteBundle\Security\Authentication\Provider;
// ...
class WebsiteUserProvider implements UserProviderInterface
{
private $em;
private $websiteId;
private $userEntity;
public function __construct($websiteListener, EntityManager $em)
{
$this->em = $em;
$this->websiteId = $websiteListener->getWebsiteId(); // Try to get the website id from my listener, but it's method onDomainParse is not called in time
$this->userEntity = 'Sybio\Bundle\CoreBundle\Entity\User';
}
public function loadUserByUsername($username)
{
// I need the websiteId here to identify the user by its username and the website:
if ($user = $this->findUserBy(array('username' => $username, 'website' => $this->websiteId))) {
return $user;
}
throw new UsernameNotFoundException(sprintf('No record found for user %s', $username));
}
// ...
}
So any idea will be appreciate ;)
I spent a lot of time to set up my authentication configuration, but now I can't get the websiteId in time, too bad :(
Thanks for your anwsers !
EDIT:
I had also other files of my authentication system to understand, I don't think I can control the provider position when loading, because they're witten in the security.yml config:
WebsiteAuthenticationProvider:
// ...
class WebsiteAuthenticationProvider extends UserAuthenticationProvider
{
private $encoderFactory;
private $userProvider;
/**
* #param \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider
* #param UserCheckerInterface $userChecker
* #param $providerKey
* #param EncoderFactoryInterface $encoderFactory
* #param bool $hideUserNotFoundExceptions
*/
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
{
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
$this->encoderFactory = $encoderFactory;
$this->userProvider = $userProvider;
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
$user = $token->getUser();
if ($user instanceof UserInterface) {
return $user;
}
try {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
} catch (UsernameNotFoundException $notFound) {
throw $notFound;
} catch (\Exception $repositoryProblem) {
throw new AuthenticationServiceException($repositoryProblem->getMessage(), $token, 0, $repositoryProblem);
}
}
// ...
}
The factory:
// ...
class WebsiteFactory extends FormLoginFactory
{
public function getKey()
{
return 'website_form_login';
}
protected function getListenerId()
{
return 'security.authentication.listener.form';
}
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
$provider = 'security.authentication_provider.sybio_website.'.$id;
$container
->setDefinition($provider, new DefinitionDecorator('security.authentication_provider.sybio_website'))
->replaceArgument(0, new Reference($userProviderId))
->replaceArgument(2, $id)
;
return $provider;
}
}
SybioWebsiteBundle (dependency):
// ...
class SybioWebsiteBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new WebsiteFactory());
}
}
Security:
security:
firewalls:
main:
provider: website_provider
pattern: ^/
anonymous: ~
website_form_login:
login_path: /login.html
check_path: /login
logout:
path: /logout.html
target: /
providers:
website_provider:
id: sybio_website.user_provider
Firewall::onKernelRequest is registered with a priority of 8 (sf2.2). A priority of 9 should ensure that your listener is called first (works for me).
I had a similar problem, which was to create subdomain-specific "Campaign" sites within a single sf2.2 app: {campaign}.{domain} . Every User has many Campaigns and I, like you, wanted to prevent a User without the given Campaign from logging in.
My solution was to create a Doctrine filter to add my campaign criteria to every relevant query made under {campaign}.{domain}. A kernel.request listener (with priority 9!) is responsible for activating the filter before my generic user provider tries to loadUserByUsername. I use mongodb, but the idea is similar for ORM.
The best part is that I'm still using stock authentication classes. This is basically all there is to it:
config.yml:
doctrine_mongodb:
document_managers:
default:
filters:
campaign:
class: My\Filter\CampaignFilter
enabled: false
CampaignFilter.php:
class CampaignFilter extends BsonFilter
{
public function addFilterCriteria(ClassMetadata $targetMetadata)
{
$class = $targetMetadata->name;
$campaign = $this->parameters['campaign'];
$campaign = $campaign instanceof Campaign ? $campaign->getId() : $campaign;
if ($targetMetadata->hasField('campaign')) {
return array('campaign' => $this->parameters['campaign']);
}
if ($targetMetadata->hasField('campaigns')) {
return array('campaigns' => $this->parameters['campaign']);
}
return array();
}
}
My listener is declared as:
<service id="my.campaign_listener" class="My\EventListener\CampaignListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="9" />
<argument type="service" id="doctrine.odm.mongodb.document_manager" />
</service>
The listener class:
class CampaignListener
{
private $dm;
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if ($campaign = $request->attributes->get('campaign', false)) {
$filters = $this->dm->getFilterCollection();
$filter = $filters->enable('campaign');
$filter->setParameter('campaign', $campaign);
}
}
}
'campaign' is available in the request here thanks to my routing configuration:
campaign:
resource: "#My/Controller/CampaignController.php"
type: annotation
host: "{campaign}.{domain}"
defaults:
campaign: test
domain: %domain%
requirements:
domain: %domain%
.. and %domain% is a parameter from config.yml or config_dev.yml
Like the response provide by benki07 it's a question of prority, you have to put your listener before the Firewall::onKernelRequest
Then, your listener will be called -> Firewall is call and your authentification listener are called with the webSiteId registered.
As you can see in the SecurityExtension.php The factories used do not have any sort of priority system. It just adds your factory to the end of the array, that's it.
Therefore it is impossible to put your custom authentication before that of symfony's security component.
An option may be to override the DaoAuthenticationProvider class paramater with your class. I hope that symfony2 will change from factories to a registry where you can add your custom authentication with a tag and a priority because this is not open/closed enough for me.

Symfony 2 & FOSUserBundle : authenticate user after resetting password

When overriding FOSUserBundle resetting password controller, there is a function call to "authenticateUser" method (line 104) :
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/ResettingController.php#L104
....
$this->authenticateUser($user);
....
My problem is that I already override the Symfony authentication handler, and have my own logic when a user logs in.
EDIT
Here is my authentication handler :
<?php
/* ... all includes ... */
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, LogoutSuccessHandlerInterface
{
private $router;
private $container;
public function __construct(Router $router, ContainerInterface $container)
{
$this->router = $router;
$this->container = $container;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// retrieve user and session id
$user = $token->getUser();
/* ... here I do things in database when logging in, and dont want to write it again and again ... */
// prepare redirection URL
if($targetPath = $request->getSession()->get('_security.target_path')) {
$url = $targetPath;
}
else {
$url = $this->router->generate('my_route');
}
return new RedirectResponse($url);
}
}
So, How could I call the "onAuthenticationSuccess" method from my authentication handler in the ResettingController ?
In order to avoid rewriting the same code...
Thanks for your help !
Aurel
You should call your onAuthenticationSuccess method loading it as a service. In your config.yml:
authentication_handler:
class: Acme\Bundle\Service\AuthenticationHandler
arguments:
container: "#service_container"
And then, call it in the authenticateUser function:
protected function authenticateUser(UserInterface $user) {
try {
$this->container->get('fos_user.user_checker')->checkPostAuth($user);
} catch (AccountStatusException $e) {
// Don't authenticate locked, disabled or expired users
return;
}
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
$this->container->get('security.context')->setToken($token);
$request = $this->container->get('request');
$this->container->get('authentication_handler')->onAuthenticationSuccess($request, $token);
}
this do the trick and pass through your custom auth handler. More info.

Access doctrine from authentication failure handler in Symfony2

I'm trying to write some loggin failure info in database from a custom authentication handler.
My problem is to gain access to the database since I don't know where the Doctrine object might be stored
Here's my code for now :
namespace MyApp\FrontBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request as Request;
use Symfony\Component\HttpFoundation\RedirectResponse as RedirectResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\Security\Http\Authentication as Auth;
use Symfony\Component\Security\Core\Exception\AuthenticationException as AuthException;
class SecurityHandler implements Auth\AuthenticationFailureHandlerInterface
{
public function onAuthenticationFailure(Request $request, AuthException $token)
{
try
{
$lastLoginFailure = new DateTime();
// get database object here
}
catch(\Exception $ex)
{
}
}
}
Any ideas ?
Turn your SecurityHandler into a service and then inject the doctrine entity manager into it.
http://symfony.com/doc/current/book/service_container.html
Start command php app/console container:debug.
Copy doctrine.orm.entity_manager and paste to your hadler constructor arguments like
[...., #doctrine.orm.entity_manager].
In hadler use Doctrine\ORM\EntityManager;
I think you should extends your class "SecurityHandler" with ContainerAware if you want to use service since your Security Handler is not a controller.
class SecurityHandler extend ContainerAware implements Auth\AuthenticationFailureHandlerInterface{
public function onAuthenticationFailure(Request $request, AuthException $token)
{
try
{
$lastLoginFailure = new DateTime();
// get database object here
$doctrine = $this->container->get('doctrine');
$repository = $doctrine->getRepository('*NAME OF REPO*');
}
catch(\Exception $ex)
{
}
}
}