Symfony2 authentication provider: authenticate against webservice

The past few days I have really be struggeling with the Symfony2 security component. It is the most complex component of Symfony2 if you ask me! On the symfony.com website there is a pretty neat cookbook article about creating a custom authentication provider. Despite the fact that it covers the subject pretty well, it lacks support for form-based authentication use cases. In the current Symfony2 project I’m working on, we’re dealing with a web service that we need to authenticate against. So the cookbook article was nothing more then a good introduction unfortunately.

Using DaoAuthenticationProvider as example

Since we don’t want to reinvent the wheel, a good place to start is by investigating the providers that are in the Symfony2 core. The DaoAuthenticationProvider is a very good example, and used by the default form login. We are going to add a few pieces of code, so we can use the listener and configuration settings. The only thing we want to change are the authentication itself and the user provider. If you take a look at the link above, you will see the only thing we need to change is the checkAuthentication method. But, a few more steps are needed in order to make things function correctly. Let’s begin! :)

We also need a UserProvider!

First things first: we need a custom user provider. The task of the user provider is load the user from a source so the authentication process can continue. Because a user can already be registered at the webservice a traditional database user provider won’t work. We need to create a local record for every user that registers or logs in and doesn’t have an account. So basically the user provider is only responsible for loading and creating a user record. In this example I save the user immediately when there is no record; probably you want to do this after authenticating.

The code for the use provider looks like this:

We add it to our services configuration in app/config/services.yml:

Creating the AuthenticationProvider

As I said earlier we are going to base our provider on the DaoAuthenticationProvider. In my bundle I created a new class called ServiceAuthenticationProvider. Like our example we are extending the abstract UserAuthenticationProvider. Besides the checkAuthentication method we also must implement the retrieveUser method. We inject the service through the constructor, so the class looks like this:

Note the call to $this->service->authenticate where the magic happens. The retrieveUser method receives a User instance from our user provider. Although this is not really clear in the code above, it will be after configuration in the service container. We use the configuration from the Symfony core and adjust it to our needs:

Please note the empty arguments. Look a bit strange, huh? These will be magically filled when the container is build by our Factory! This is a bit tricky, and the cookbook explains pretty wel, so I suggest to take a look there. We are extending the FormLoginFactory because we want to change it bit:

Add the builder in the Acme\DemoBundle\AcmeDemoBundle.php file:

Finally, change your security config:

The webservice-login key activates our authentication provider. The user provider is defined under providers as acme_provider with the corresponding service id.
I used the AcmeDemo bundle from symfony-standard repository, so you could just copy paste most of my code to see everything in action! Only thing you need to provide yourself is a dummy webservice.

Happy coding!

37 thoughts on “Symfony2 authentication provider: authenticate against webservice

  1. Nice post, did put me on the right track.. I only get one error.

    $extension->addSecurityListenerFactory(new SecurityFactory());
    The method addSecurityListenerFactory does not exist. Which version of symfony do you use?

    1. Hi there! Sorry for the late response but the spam comments are really killing me on my blog… I missed your comment.

      I am using Symfony 2.1 master branch at the moment. Are you on 2.0?

    2. To get this to work with Symfony 2.0.x follow the instructions on .
      You do not need to edit Acme\DemoBundle\AcmeDemoBundle.php, instead you should create the file “security_factories.yml” according to the Symfony cookbook.

  2. Great, I made it work, thanks for your article!
    However I’ve used .yml for service configuration:

    security.authentication_provider.acme_demo_webservice:
    class: %acme.service.class%
    arguments: [“@acme_demo_webservice”, “”, “@security.user_checker”, “”, “@security.encoder_factory”]

    1. The Acme\DemoBundle\Service\Service is just an imaginary webservice to authenticate against. It should be the PHP client for the webservice you are using.

        1. Hi Samurai K,

          The source of the client I’m using is private so I cannot share it. In the first place: Why are you trying to authenticate with a webservice when you don’t have a webservice and/or client? Do you have a webservice which provides authentication? What is your use case?

          I can supply an example of a client but I don’t see the actual use of it.

  3. Hello ricbra, really nice article that helped me to understand how to set a custom user form. In addition, I would like to know if it’s possible to change the comportment of “$this->userProvider->loadUserByUsername”, because i need also to load a user by his username AND his websiteId (a second property of my User class). Do you have an idea how to perform this ?

    I posted my problem on the symfony forum here: http://forum.symfony-project.org/viewtopic.php?f=23&t=68449

    Now I know that I need to replace my SecurityFactoryInterface by a FormLoginFactory, that’s not so bad 😉

    1. Hi Sybio!

      The “loadUserByUSername” method is defined in the Symfony\Component\Security\Core\User\UserProviderInterface and you cannot change the method signature I’m afraid.

      You could create your own UserProvider and then inject the request via the container so you could read the current website through the request instead. Or won’t that work?

      Good luck! :)

  4. You don’t even know how much help you’ve already been! I’ve been struggling for days to find a way to auth with an external api. Thanks to you I’m up and running. A quick question though:

    What is the best way to keep the passed password encrypted? Currently I have set the encoder to plaintext as in your example, and I persist the User to the DB once the API has validated the credentials (in checkAuthentication() ).

    If I “$user->setPassword($presentedPassword);” with plaintext encoding I have no problem going forward (the user continues to be authenticated) however if I encode the password with the encoder factory set to sha512 or similar and then set “$user->setPassword($encodedPassword);”, the user no longer is authenticated passed the login_check route.

    I think it has something to do with the fact that the User’s password has changed (from plain to encoded) however the credentials in the token have not changed and so it boots the user out of the authenticated state.

    Any suggestions?

    1. I wasn’t storing the password (they aren’t of any use, because authentication happens somewhere else) so that’s why I don’t encode them. Why are you storing them anyway?

      Best solution in your case is to have a plaintextPassword property (not mapped to Doctrine) and an encodedPassword property which you persist.

  5. I’m storing them because down the road I’m planning on building a replacement for the web service I’m currently authenticating against. since all the users will be stored, when we make the switch, there won’t be any users to migrate.

    thanks for the tip!

  6. Thanks a lot for your article, ricbra. I spent days trying to implement the same kind of authentication without success. The Symfony cookbook article is not so simple to understand. Yours was in contrary very helpful.

    Thanks again.

  7. Hi!
    Thanks for your article!

    I have a question though. Is it normal that loadUserByUsername (which calls the WS) is called twice at each page (once in refreshUser ans then once in the custom Provider)? I don’t know if this is a good thing.

    In my case, once I have retrived my users data on login, I don’t actually need to call the WS again.
    The problem I have is that if I do not call loadUserByUsername in refreshUser, my Listener and Porivder are never called, thus my token is never verified and my session never expires.

    Is that normal? Can you help me, please?

    1. Hi Marie,

      That’s normal. I suggest to keep a local record of your users (in database or in at least in session) so you only connect once to your webservice.

      1. Hi Ricbra. Thanks for this amazing article!

        I have the same question as Marie. I am still a newbie with Symfony, so i don´t understand very well how to keep the user in session to avoid connecting again to the webservice. Could you please tell me how to do it.

    1. Hi Tom,

      You’re correct. EpsAuthenticationProvider and ServiceAuthenticationProvider are the same, I copied the code more or less from my project at work that time :).

      1. Do you have any idea why I would be getting an error:

        Unrecognized options “webservice_login” under “security.firewalls.main” ?

  8. Ricbra,
    Let’s say I want to this a step further and want to use my web service to also return roles that user is part of, where should I do this? Should I do this in WebserviceUserProvider and set the roles in there also?

  9. Hi Ricbra,

    Great post, it has really helped me to get this far, thank you. I am now struggling with how to actually get the user information from the web service. I am looking to send the username and password to the external API which will then feed back the information for me to populate a user object.

    In your instance, the user data is populated before the API is called and it is just authenticated against the API afterwards. How would you go about collecting user data from the API?

    Thanks,
    Matt

      1. Hi ricbra,
        I’m trying to conect to the web service and grab user data to an User object.
        How would you go about collecting user data from the API?

  10. Hi, Neat post. There is a problem along with your site in internet explorer, would check this?
    IE nonetheless is the marketplace leader and a huge portion of other folks will leave out your excellent writing because of this problem.

  11. Hi Ric,

    I have faced a problem that I implemented successfully custom authentication when credential is correct. but I found an exception of bad credentials or The user provider must return a UserInterface object. Can you give me an idea about this problem.

    Following are files to implement custom authentication provider. I have already spent a week to remove this exception but still I am not succed.

    Please refer below links about source code.

    CollegelifeSecurityProvider.php :
    Path : /opt/lampp/htdocs/collegelife/src/Collegelife/SecurityBundle/Security/CollegelifeSecurityProvider.php
    Link : https://www.dropbox.com/s/eryckij4qsyoxmd/CollegelifeSecurityProvider.php?dl=0

    services.yml:
    Path : /opt/lampp/htdocs/collegelife/src/Collegelife/SecurityBundle/Resources/config/services.yml
    Link : https://www.dropbox.com/s/2g936b8mbu41xdx/services.yml?dl=0

    Admin.php:
    Path : /opt/lampp/htdocs/collegelife/src/Collegelife/CommonBundle/Entity/Admin.php
    Path : https://www.dropbox.com/s/dffcipfvoj4rcjr/Admin.php?dl=0

    LoginController.php:
    Path : /opt/lampp/htdocs/collegelife/src/Collegelife/SecurityBundle/Controller/LoginController.php
    Link : https://www.dropbox.com/s/pbdeza5seu3noiq/LoginController.php?dl=0

    security.yml:
    Path : /opt/lampp/htdocs/collegelife/app/config/security.yml
    Link : https://www.dropbox.com/s/xyhkwamgsjrj7py/security.yml?dl=0

    1. Hi Dipak,

      Sorry for the late response. Somehow my mail got spammed so didn’t see your comment. In case you need help with specific problems in your codebase I suggest you to try at stackoverflow.com

      What kind of Exception is being thrown? Perhaps you could place your code in Github Gists so its easier to read for fellow developers.

      Regards,
      Richard

  12. Please

    [Symfony\Component\Debug\Exception\ContextErrorException]
    Catchable Fatal Error: Argument 1 passed to Ges\UserBundle\Twig\Extension\U
    serExtension::__construct() must implement interface Twig_LoaderInterface,
    none given, called in C:\wamp\www\CCGG\app\cache\de_\ap_DevDebugProjectCont
    ainer.php on line 3253 and defined

    1. Hi,

      Unfortunately this blogpost is 3 years old and a lot has changed in Symfony world since then. Perhaps the UPDATE.md files in the Symfony repo can shed some light on your issues?

      Best,
      Richard

  13. Thank you very much for this post with its helpful explanation and code samples. It helped me understand this topic better…

Leave a Reply

Your email address will not be published. Required fields are marked *