Custom form authentication

New topics about Symfony 2 should go here

Moderators: tiagojsag, dcobalt

Custom form authentication

Postby ChristopheL » Tue Nov 22, 2011 10:46 pm

Hello,

In order to authenticate on my web app, the requirements are:
- a login page with a form containing 3 input text: server / username / password
- use a web service in order to authenticate the user using the above information

Since there is already a login page with a form containing the fields username / password in the standard Symfony2 project, I choose to simply add a field 'server' in the form and plug my authentication process using the webservice in the existing Form Authentication process built-in in Symfony2.

Rather than creating a custom Authentication Provider as described in the cookbook
http://symfony.com/doc/2.0/cookbook/sec ... vider.html
I wanted to use as much as of the existing code of the built-in form authentication and use dependency injection in order to plug my custom implementation.

I described below the way I implemented it (I have ommited the LogoutHandler since its implementation is following the recommendation from the documentation).

It is working quite well, but since I'm new to Symfony2 and that the final solution doesn't look as clean as it was promising at the beginning, could you please comment it ?

Thanks in advance for your feedback.

Best regards,
Christophe

Step 1)
Creation of a custom Token which extends UsernamePasswordToken in order to add my 'server' information in it

Code: Select all
class VimToken extends UsernamePasswordToken
{
   private $server;
        ....


No problem in this step.

Step 2)
Creation of an AuthenticationListener.
Unfortunately, I have not been able to extend the 'UsernamePasswordFormAuthenticationListener' since the field $csrfProvider was declared as private without any accessor for subclass.

So I copy/paste the class 'UsernamePasswordFormAuthenticationListener' in my bundle, renaming it VimFormAuthenticationListener.
Then, I changed the attemptAuthentication(Request $request) method, adding one line of code and replacing the final return statement:

Code: Select all
//add a line of code for retrieving the 'server' request parameter
$server = $request->get($this->options['server_parameter'], null, true);

// replace the return statement which was originally returning the UsernamePasswordToken so that it returns a custom Token instance with the 'server' information
return $this->authenticationManager->authenticate(new VimToken($server, $username, $password, $this->providerKey, array(), null));


This step 2 is not clean since it copy/paste 99% of a framework class, but I have not found a way to keep all the form authentication functionnalities without doing it.

Step 3)
Creation of a AuthenticationProvider.
An implementation of AuthenticationProviderInterface, with injection of my webservice wrapper in the constructor.

Code: Select all
class VimAuthenticationProvider implements AuthenticationProviderInterface
{
   private $vimService;
   
   function __construct($vimService)
   {
      $this->vimService = $vimService;
   }
   
   function getVimService()
   {
      return $this->vimService;
   }
   
   function supports(TokenInterface $token)
   {
      return $token instanceof VimToken;
   }
   
   function authenticate(TokenInterface $token)
   {
      if (!$this->supports($token)) {
                    return null;
                 }

                 // calling the webservice for authenticating the server / username / password info here
   
                return $token;
   }
}


No problem in this step.

Step 4)
Creation of a Factory extending the FormLoginFactory.

Actually, at the beginning, I was thinking I wouldn't need to do this, and simply use dependency injection.
Unfortunately, the code of the createAuthProvider of the FormLoginFactory expects to have an AuthenticationProvider with at least 3 arguments in the constructor, and as we have seen above, it is not the case of the custom AuthenticationProvider.
So I was a bit sorry not to be able to use only dependency injection due to this 'required' arguments, but the cleanest way I found was to extend the FormLoginFactory and override the createAuthProvider, removing the replaceArgument calls.

Code: Select all
class VimFormLoginFactory extends FormLoginFactory
{
   public function __construct()
    {
       parent::__construct();
   }
   
   protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
    {
        $provider = 'security.authentication.provider.dao.'.$id;
        $container
            ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao'))
// the two lines below have been commented since VimAuthenticationProvider doesn't have those arguments         
//            ->replaceArgument(0, new Reference($userProviderId))
//            ->replaceArgument(2, $id)
        ;

        return $provider;
    }
}


Step 5)
use the dependency injection in order to override the built-in service implementation:

in security_factories.xml, I use the same service id 'security.authentication.factory.form' but with my custom Factory
Code: Select all
<service id="security.authentication.factory.form" class="MyBundle\DependencyInjection\Security\Factory\VimFormLoginFactory">
            <tag name="security.listener.factory" />
        </service>


in security_listeners.xml, I use the same service services id 'security.authentication.listener.form' and 'security.authentication.provider.dao', but with the custom listener and authentication provider class.
Code: Select all
<parameters>
        <parameter key="security.authentication.listener.form.class">MyBundle\Security\Firewall\VimFormAuthenticationListener</parameter>
        <parameter key="security.authentication.provider.dao.class">MyBundle\Security\Authentication\Provider\VimAuthenticationProvider</parameter>
    </parameters>

    <services>
 
        <service id="security.authentication.listener.form"
                 class="%security.authentication.listener.form.class%"
                 parent="security.authentication.listener.abstract"
                 abstract="true">
        </service>
       
        <service id="security.authentication.provider.dao"
           class="%security.authentication.provider.dao.class%"
           abstract="true"
           public="false">                  
            <argument type="service" id="vim.service" />
        </service>


Step 6)
Finally, declaring the factory in the security.yml file, in order to override the built-in factory

Code: Select all
security:
    factories:
      - "%kernel.root_dir%/../src/MyBundle/Resources/config/security_factories.xml"


and importing the security_listeners.xml file

Code: Select all
imports:
    - { resource: parameters.ini }
    - { resource: security.yml }
    my_bundle:
      resource: @MyBundle/Resources/config/security_listeners.xml   
ChristopheL
Junior Member
 
Posts: 27
Joined: Tue Nov 22, 2011 9:55 pm

Re: Custom form authentication

Postby zhil » Fri Nov 25, 2011 11:18 pm

nice manual, thanks for sharing :)

no ideas about what can be done better :)
User avatar
zhil
Senior Member
 
Posts: 147
Joined: Tue Sep 13, 2011 5:16 pm


Return to General Symfony 2 discussion

Who is online

Users browsing this forum: Google Feedfetcher, Yahoo [Bot] and 4 guests

cron