KnpUGuard: Symfony Authentication with a Smile

Symfony's authentication is definitely powerful, but if you need to do something complex, you might pull your hair out.

Introducing KnpUGuard authentication, which puts all the complexities of any authentication scheme right at your finger-tips. The code you right will be easy to understand and customizing things to any whim will be simple:

  • Authentication via an API token
  • "Social login" / OAuth authentication
  • Creating a traditional login form
  • Customizing success/failure behavior
  • Customizing error messages
  • Loading users in complex ways
  • ... probably anything else

If all goes well, Guard will become a core part of Symfony. If you like it, let people know it worked for you at symfony/symfony#14673. And if it doesn't cover a use-case, open an issue and let me know!


Your Guides

Ryan Weaver

Questions? Conversation?

  • 2016-05-11 weaverryan

    I can't tell you that exactly, but I might be able to help. Support for Guard in Silex was just merged: https://github.com/silexphp/Si.... Unfortunately, it was merged into Silex 1.0 only, and it hasn't been released yet, so you would need to use the unreleased Silex 2 to use it.

    If you're using Silex 1, you'll need to create a custom authentication provider (http://silex.sensiolabs.org/do... - and basically try to "mimic" the above pull request. Once Silex 2 is released, it'll be very easy - but it's not *quite* there yet!

    Cheers!

  • 2016-05-10 Russell Seymour

    Hello, this looks great and exactly what I am looking for. However I am using Silex, do you know when the chapter on using Guard Authentication in Silex will be written?
    Thanks.

  • 2016-03-29 weaverryan

    Hi there!

    I think I understand :). Yes, you can use FosUserBundle and *not* have a UserBundle (only have an AppBundle). Anything that you might normally put into UserBundle, just put into AppBundle. If you need to override templates, you can do that by putting the templates in app/Resources/views... http://symfony.com/doc/current....

    The *only* limitation is that - with just one AppBundle - you probably won't be able to use "bundle inheritance" to override things in FOSUserBundle: http://symfony.com/doc/current.... But, that's not necessary - you can override everything in other ways. The only thing you will not be able to override are the controllers from FOSUserBundle. But, that's *also* ok - you should not override its controllers, you should hook into its event system to customize behavior: https://symfony.com/doc/master...

    Phew! I hope that helps!

  • 2016-03-28 elh

    hello ,
    Thank u for this tutoriel
    Can u tell me if its possible to use FosUserBundle without the UserBundle that 's meen having just nth AppBundle ?

  • 2016-03-13 Eduard Pleh

    Hey Ryan.

    Thank you for guard auth extension that you wrote!
    Symfony 2 auth system always was the 'pain' point for our team of developers.(I think not only for our team)
    Now it works like a charm. Thank you again.

  • 2015-12-20 B Roeser

    Hey Ryan,

    My blog post is online. I hope its correct. *g*:
    http://benedictroeser.de/2015/...

  • 2015-12-18 B Roeser

    Thank you very much! The information about the factory class is very helpful.

    When I'm done and it works I'll share my findings e.g. in a blog post. :)

  • 2015-12-18 weaverryan

    Hey there!

    Wow, I'm impressed by what you're doing :). To your question: the hardest part of getting this all setup will actually be just getting the security system bootstrapped correctly (which you already have code for - I can't say for sure if it's correct, but it certainly looks good). As you probably already know, Guard is just an authentication provider system, much like how "form_login" activates a listener+provider. Obviously, this is all pre-configured in the DI for you in the framework.

    Here's a class that might help: https://github.com/symfony/sym.... These "factory" classes are weird, but basically they're wiring up the DI container for the services necessary for Guard. You can translate that into the direct object equivalents.

    Also, providerKey == your firewall name - in the framework, that's the key in security.yml for your firewall (e.g. "main"). It's not important, except that if you have multiple firewalls, you can configure different classes for different firewalls based on this key. But to be honest, I'm not sure exactly where you actually setup this key in the first place. If I were you and got stuck on this, I'd grab the code from this repository (or build a simple Symfony SE project using Guard) and then look at the dumped container to see how the objects are wired together. And the UserChecker should be this: https://github.com/symfony/sym.... It's totally not important, and in theory, would be made optional.

    I hope that helps and good luck!

  • 2015-12-18 B Roeser

    Hey Ryan,

    Guard looks very interesting!
    I'm just getting started with Symfony and I'm interested in using the Symfony components (not a whole AppBundle) to refactor an existing system. I used this manual to do that:
    http://symfony.com/doc/current...
    and I could already replace homespun solutions of routing, i18n, caching, ... with Symfony components that are working much better and are more felxible.

    My next logical step is to replace the existing authentication system (form based) with Guard. I already wrote the FormLoginAuthenticator and UserProvider, as you described in the chapter "How to Create a Login Form". I currently neither use the Symfony DI nor the Symfony YAML-configuration, because I wanted to keep the existing solutions in place. Is it even possible to use Guard without the Symfony DI component? Also, do you know if there's a manual somewhere on using Guard without the whole Symfony framework?

    I added some code to my "Framework"-class that looks basically like that:

    $firewallMap = new Http\FirewallMap('^/');
    $requestMatcher = new HttpFoundation\RequestMatcher();
    $this->tokenStorage = new \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage();
    $firewallListeners = array();
    $guardHandler = new \Symfony\Component\Security\Guard\GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
    $guardAuthenticators = array(new FormLoginAuthenticator());
    $providers = array(new \Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider($guardAuthenticators, $userProvider, ??????????
    ...
    $firewallMap->add($requestMatcher, $firewallListeners);
    $this->firewall = new Http\Firewall($firewallMap, $dispatcher);
    $this->dispatcher->addListener(\Symfony\Component\HttpKernel\KernelEvents::REQUEST, array($this->firewall, 'onKernelRequest'));

    Now I am stumped. Am I on the right track so far?
    The Guard AuthenticationProvider requires a providerKey and an UserChecker, I don't quite get, what that means. Would you mind explaining?

  • 2015-12-14 weaverryan

    Hey Paul!

    Yea, it *is* now part of Symfony 2.8 and 3.0 :). So, the Guard library (and bundle) are being deprecated: you should switch to the core version of the feature once you upgrade to Symfony 2.8. We have some details here: https://github.com/knpuniversi....

    Then, we'll update this tutorial soon to be for the core version of the library.

    Cheers!

  • 2015-12-14 Paul Okeke

    Hi, Any word on this becoming a core part of Symfony??

  • 2015-09-16 weaverryan

    Good tip! That's a classic thing to watch out for - it's not a problem unless you, for example, have an access_control that requires login on every URL (e.g. ^/). An alternative (but not better than yours) solution is to put an access_control allowing ^/login$ above the access_control that requires login for everything else - something like


    access_control:
    # only the first matching access_control entry is used on any request
    # allow anonymous access to /login and /login_check
    - { path: ^/login_check$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    # for all non /login or /login_check URLs, require ROLE_USER
    - { path: ^/, roles: ROLE_USER }

    Cheers!

  • 2015-09-16 Mathew Peterson

    I was getting redirect loops but I was able to fix it by creating a separate firewall for ^/login$.

  • 2015-09-07 Neandher Carlos

    \o/ I'm glad to help you.

  • 2015-09-07 weaverryan

    You're awesome for prepping that! And yes, it was a bug in the code from the library (and your fix above is correct). I've fixed this and just tagged a new release, which you should be able to use immediately. https://github.com/knpuniversi...

    Thanks!

  • 2015-09-04 Neandher Carlos

    Take a look: https://github.com/neandher/Sy...

    For this works, i have to comment this lines in file GuardAuthenticationProvider.php:

    /*
    throw new \LogicException(sprintf(
    'The correct GuardAuthenticator could not be found for unique key "%s".
    The listener and provider should be passed the same list of authenticators.',
    $token->getGuardProviderKey()
    ));
    */
  • 2015-09-03 weaverryan

    Hey!

    Don't worry about your English, it seems very good to me, and it's certainly better than my Portuguese!

    Would it be possible to post some of your code to a repository online? You should NEVER see the error that you see - it should basically be impossible, no matter what your setup looks like. So, it makes me wonder if there is a bug in the core library itself. If you're able to post a small repository with enough code to see the error, that would be a HUGE help.

    Thanks!

  • 2015-09-03 weaverryan

    Hey Mathew!

    It could be several weeks, so perhaps I can help here sooner :). What issues are you running into? For the most part, FOSUserBundle is nothing more than a User entity, a User provider, and a bunch of routes/controllers (for things like logging in, registering, etc). So, if you're using Guard to have some new authentication method, in theory, you'd simply create and save your User entity... and that should be it - FOSUserBundle shouldn't get much in the way. So, what are you seeing?

    Cheers!

  • 2015-09-03 Mathew Peterson

    Do you know when the FOSUserBundle chapter will be released? I'm trying to integrate FOSUserBundle into an app that already has Guard configured but I am running into issues.

  • 2015-09-02 Neandher Carlos

    Hey again. I tried to implement this, but without success. In "Gestor" secured area is ok, but in "Admin" return a error.

    gestor:
    anonymous: ~
    pattern: ^/gestor
    knpu_guard:
    authenticators:
    - app.gestor_form_login_authenticator
    logout:
    path: gestor_security_logout
    target: gestor_security_login

    admin:
    anonymous: ~
    pattern: ^/admin
    knpu_guard:
    authenticators:
    - app.admin_form_login_authenticator
    logout:
    path: admin_security_logout
    target: admin_security_login

    Message error:

    The correct GuardAuthenticator could not be found for unique key "admin_0". The listener and provider should be passed the same list of authenticators

    :(

  • 2015-08-27 Neandher Carlos

    Hey! Thanks for answering :)

    No no no! I have 2 firewalls and one UserInterface: AppBundle/Entity/User, where i have the login and password attributes and other things. In AdminUser Table and CustomerUser Table I have FirstName and LastName and other things, and do a join with the User Table to inherit their attributes. So when I go to log in "admin" or "customer" I have this in UserRepository (UserInterface):

    public function findAdminUserByEmail($emailCononical)
    {
    return $this->createQueryBuilder('u')
    ->innerJoin('u.adminUser','g') // <= <= <=
    ->andWhere('u.emailCanonical = :emailCanonical')->setParameter('emailCanonical', $emailCononical)
    ->andWhere('u.roles like :role')->setParameter(':role', '%ROLE_ADMIN_USER%') // <= <= <=
    ->getQuery()
    ->getOneOrNullResult();
    }

    public function findCustomerUserByEmail($emailCononical)
    {
    return $this->createQueryBuilder('u')
    ->innerJoin('u.customerUser','g') // <= <= <=
    ->andWhere('u.emailCanonical = :emailCanonical')->setParameter('emailCanonical', $emailCononical)
    ->andWhere('u.roles like :role')->setParameter(':role', '%ROLE_CUSTOMER_USER%') // <= <= <=
    ->getQuery()
    ->getOneOrNullResult();
    }

    In this way I have One (User) To One (AdminUser or CustomerUser), then AdminUser can not log in CustomerUser.

    PS: Sorry for my english, i am brazilian.

  • 2015-08-27 weaverryan

    Hey!

    I think this setup makes sense. By multiple authenticators, I'm technically talking about using 2 authenticators on the same firewall, but whether you should have 1 firewall or 2 firewalls depends on your setup. Often, even if you have multiple ways to authenticate, it's convenient to always have the *same* one User object, no matter how you login. In your case, if I login under the customer firewall, will the User object be CustomerUser? And then if I login via "admin", it will be AdminUser? In that case, you just need to be careful - if you have code (e.g. a base template) that is used by pages under both firewalls, then "app.user" could sometimes be a CustomerUser and sometimes be an AdminUser. That's that reason why I typically like to always have the same User object in all cases. But if you really have 2 separate sections of your site, having 2 firewalls and 2 different UserInterface objects makes more sense. If you go down this road, you'll need a user provider for each user type - because when the user is loaded from the session, the UserProvider is used to re-query for the user. User providers are still kind of a complicated part of all of this.

    Or maybe I've not understood your situation entirely. But anyways - it depends on your setup, but what you have hear largely makes sense.

    Cheers!

  • 2015-08-25 Neandher Carlos

    About multiple authenticator, what do you think about this?

    customer:
    anonymous: ~
    pattern: ^/customer
    knpu_guard:
    authenticators:
    - app.customer_form_login_authenticator
    logout:
    path: customer_security_logout
    target: customer_security_login

    admin:
    anonymous: ~
    pattern: ^/admin
    knpu_guard:
    authenticators:
    - app.admin_form_login_authenticator
    logout:
    path: admin_security_logout
    target: admin_security_login

    ##### In User - Entity #####

    /**
    * @ORM\OneToOne(targetEntity="AppBundle\Entity\Customer\CustomerUser", mappedBy="user")
    */
    private $customerUser;

    /**
    * @ORM\OneToOne(targetEntity="AppBundle\Entity\Admin\AdminUser", mappedBy="user")
    */
    private $adminUser;

    ##### In CustomerUser - Entity #####

    /**
    * @ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="customerUser")
    * @ORM\JoinColumn(nullable=false)
    * @Assert\Valid()
    */
    private $user;

    ##### In AdminUser - Entity #####

    /**
    * @ORM\OneToOne(targetEntity="AppBundle\Entity\User", inversedBy="adminUser")
    * @ORM\JoinColumn(nullable=false)
    * @Assert\Valid()
    */
    private $user;

    ##### In UserRepository #####

    public function findCustomerUserByEmail($email)
    {
    ...
    }

    public function findAdminUserByEmail($email)
    {
    ...
    }
  • 2015-08-25 weaverryan

    Thomas!

    Fancy seeing you here :). The "multiple authenticators" would be more like the second - being able to have, for example, form login AND Api token authentication. Though you can do whatever, I think it's best to think of each authenticator as *one* way to "fetch" the login information for the user. For example, filling in a username/pass would be one authenticator (and in that authenticator, you might load users from the db or from LDAP, etc). Then, if you're grabbing an API token from the request, that's a different authenticator.

    For the first case (and a lot depends on the details), I'd try to fit this into one authenticator, because I think it sounds like the user is just submitting a username/password either way. Then, in that authenticator, you can decide if you want to look up that user in LDAP, and/or lookup/create an entity in the database for the user. The trick in all of this is the "user provider" - would you have 2 in this case? Again, it depends, but I'd have just one. The truth is, the user provider is only *really* needed in the system for one thing: to reload the User object on each request (for normal, session-based login). In the authenticator, we pass you the UserProvider, but you don't have to use it at all. But ultimately, once you figure out what your User object is when authenticating, you need to have one user provider that is able to "refresh" that on each request. So, if ultimately every user is stored as an entity, then you'd use the normal entity user provider.

    That's a whole wall of (un)related nonsense - just shooting details your way. Let me know if this makes sense and what you want to make happen.

    Cheers!

  • 2015-08-24 Thomas Bennett

    Hey Ryan,

    Will the "multiple authenticators" section be a multiple/chain providers example, like LDAP falling back to database, or will it be something more like database and an API as a separate authenticator?

    Thanks!

  • 2015-07-21 weaverryan

    sahweet!

  • 2015-07-21 Jonas

    Thank you very much for your time, so far its working! Have a nice day!

  • 2015-07-21 weaverryan

    Ah, of course - I forgot about the second part - the part that sets headers on the Response that tells your browser to prompt for credentials. So:

    A) Remove http_basic: true - you're not using that anymore (your authenticator is doing everything)

    B) In the start() method of your authenticator, you need to return a Response that tells your browser to prompt for credentials. Basically, you need this code verbatim (except putting in whatever realm you want): https://github.com/symfony/sym...

    Cheers!

  • 2015-07-21 Jonas

    Thank you for your response!

    Unfortunately, when I add 'knpu_guard' to the Firewall rule, I don't get prompted for credentials. (When I remove it, I get prompted)

    Here is my security.yml, may you take a look at it?


    firewalls:
    api:
    pattern: ^/api/
    http_basic: true
    knpu_guard:
    authenticators:
    - app.api_token_authenticator
    entry_point: app.api_token_authenticator
  • 2015-07-21 weaverryan

    Hey Jonas!

    Yep - that should be easy and getCredentials() *is* the right spot. Here's how to fetch Http Basic user/pass:


    public function getCredentials(Request $request)
    {
    $user = $request->headers->get('PHP_AUTH_USER');
    $pass = $request->headers->get('PHP_AUTH_PW');

    // skip trying to authenticate if no user is present
    if (!$user) {
    return;
    }

    return array('username' => $user, 'password' => $password);
    }

    Everything after that should be easy :). Let me know if it works!

  • 2015-07-21 Jonas

    is it possible to authenticate via basic auth? Like http://appToken:userToken@localhost/api/foo and get the credentials in 'getCredentials(..)'?