Buy
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We built a login form with a traditional route, controller and template. And so you might expect that because the form submits back to this same URL, the submit logic would live right inside this controller:

... lines 1 - 8
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils)
{
... lines 16 - 25
}
}

Like, if the request method is POST, we would grab the email, grab the password and do some magic.

What are Authentication Listeners / Authenticators?

Well... we are not going to do that. Symfony's security works in a bit of a "magical" way, at least, it feels like magic at first. At the beginning of every request, Symfony calls a set of "authentication listeners", or "authenticators". The job of each authenticator is to look at the request to see if there is any authentication info on it - like a submitted email & password or maybe an API token that's stored on a header. If an authenticator finds some info, it then tries to use it to find the user, check the password if there is one, and log in the user! Our job is to write these authenticators.

Understanding Firewalls

Open up config/packages/security.yaml. The most important section of this file is the firewalls key:

security:
... lines 2 - 8
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
... lines 23 - 29

Ok, what the heck is a "firewall" in Symfony language? First, let's back up. There are two main parts of security: authentication and authorization. Authentication is all about finding out who you are and making you prove it. It's the login process. Authorization happens after authentication: it's all about determining whether or not you have access to something.

The whole job of the firewall is to authenticate you: to figure out who you are. And, it usually only makes sense to have one firewall in your app, even if you want your users to have many different ways to login - like a login form or API authentication.

But... hmm... Symfony gave us two firewalls by default! What the heck? Here's how it works: at the beginning of each request, Symfony determines the one firewall that matches the current request. It does that by comparing the URL to the regular expression pattern config. And if you look closely... the first firewall is a fake!

security:
... lines 2 - 8
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
... lines 13 - 29

It becomes the active firewall if the URL starts with /_profiler, /_wdt, /css, /images or /js. When this is the active firewall, it sets security to false. Basically, this firewall exists just to make sure that we don't make our site so secure that we block the web debug toolbar or some of our static assets.

In reality, we only have one real firewall called main:

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
anonymous: true
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
... lines 23 - 29

And because it does not have a pattern key, it will be the active firewall for all URLs, except the ones matched above. Oh, and, in case you're wondering, the names of the firewalls, dev and main are totally meaningless.

Anyways, because the job of a firewall is to authenticate the user, most of the config that goes below a firewall relates to "activating" new authentication listeners - those things that execute at the beginning of Symfony and try to log in the user. We'll add some new config here pretty soon.

Oh, and see this anonymous: true part?

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
anonymous: true
... lines 15 - 29

Keep that. This allows anonymous requests to pass through this firewall so that users can access your public pages, without needing to login. Even if you want to require authentication on every page of your site, keep this. There's a different place - access_control - where we can do this better:

security:
... lines 2 - 23
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }

Creating the Authentication with make:auth

Ok, let's get to work! To handle the login form submit, we need to create our very first authenticator. Find your terminal and run make:auth:

php bin/console make:auth

Call the new class LoginFormAuthenticator.

Tip

Very soon, this command will contain more interactive questions and be able to generate your entire login form code. That's awesome! But to follow with this tutorial choose the "empty" authenticator option.

Nice! This creates one new file: src/Security/LoginFormAuthenticator.php:

... lines 1 - 2
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
// todo
}
public function getCredentials(Request $request)
{
// todo
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
// todo
}
public function checkCredentials($credentials, UserInterface $user)
{
// todo
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// todo
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// todo
}
public function start(Request $request, AuthenticationException $authException = null)
{
// todo
}
public function supportsRememberMe()
{
// todo
}
}

This class is awesome: it basically has a method for each step of the authentication process. Before we walk through each one, because this authenticator will be for a login form, there's a different base class that allows us to... well... do less work!

Instead of extends AbstractGuardAuthenticator use extends AbtractFormLoginAuthenticator:

... lines 1 - 8
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 13 - 41
}

I'll remove the old use statement.

Thanks to this, we no longer need onAuthenticationFailure(), start() or supportsRememberMe(): they're all handled for us:

... lines 1 - 8
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
public function supports(Request $request)
{
// todo
}
public function getCredentials(Request $request)
{
// todo
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
// todo
}
public function checkCredentials($credentials, UserInterface $user)
{
// todo
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// todo
}
... lines 37 - 41
}

But don't worry, when we create an API token authenticator later, we will learn about these methods. We do now need one new method. Go to the "Code"->"Generate" menu, or Command+N on a Mac, and select "Implement Methods" to generate getLoginUrl():

... lines 1 - 10
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 13 - 37
protected function getLoginUrl()
{
// TODO: Implement getLoginUrl() method.
}
}

Activating the Authenticator in security.yaml

Perfect! Unlike a lot of features in Symfony, this authenticator won't be activated automatically. To tell Symfony about it, go back to security.yaml. Under the main firewall, add a new guard key, a new authenticators key below that, and add one item in that array: App\Security\LoginFormAuthenticator:

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
... lines 14 - 15
guard:
authenticators:
- App\Security\LoginFormAuthenticator
... lines 19 - 33

The whole authenticator system comes from a part of the Security component called "Guard", hence the name. The important part is that, as soon as we add this, at the beginning of every request, Symfony will call the supports() method on our authenticator.

To prove it, add a die() statement with a message:

... lines 1 - 10
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
public function supports(Request $request)
{
die('Our authenticator is alive!');
}
... lines 17 - 41
}

Then, move over and, refresh! Got it! And it doesn't matter what URL we go to: the supports() method is always called at the start of the request.

And now, we're in business! Let's fill in these methods and get our user logged in.

Leave a comment!

  • 2019-02-13 Diego Aguiar

    And nothing? Sometimes by running "composer install" it get fixed. If not, try restarting PHPStorm... I'm running out of options =S

  • 2019-02-13 Roman Romanishyn

    Already trying(((

  • 2019-02-13 Diego Aguiar

    Hmm, try clearing the index. Go to settings -> symfony plugin -> clear index
    It may do the trick :)

  • 2019-02-13 Roman Romanishyn

    Yes. I have its both and its enabled.

  • 2019-02-13 Diego Aguiar

    Have you installed Symfony plugin? And a couple others like Twig and annotations support?

  • 2019-02-13 Roman Romanishyn

    Thank you! That's works!!! But now my ide didnt know about Controllers methotds, I'm using php storm.

  • 2019-02-12 Diego Aguiar

    Hey Roman Romanishyn

    You are missing this piece of configuration:


    services:
    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
    resource: '../src/*'
    exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
    resource: '../src/Controller'
    tags: ['controller.service_arguments']

    So almost everything inside "src/" will be auto registered as a service. And because of that you don't need to define "api.form.login"

    Cheers!

  • 2019-02-11 Roman Romanishyn

    I'm using symfony 4.2.3

    parameters:

    services:
    # default configuration for services in *this* file
    _defaults:
    autowire: true # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    App\Controller\JsonRpcController:
    public: true
    bind:
    $jsonRpcServer: '@api.jsonrpc_server'

    api.jsonrpc_reflection:
    class: App\Model\JsonRpcReflection
    arguments: ['@service_container', '@api.jsonrpc_serializer', '@annotations.reader']
    public: false

    api.jsonrpc_serializer:
    class: App\Model\JsonRpcSerializer
    arguments: ['@jms_serializer', '@jms_serializer.serialization_context_factory']
    public: false

    api.jsonrpc_server:
    class: App\Service\JsonRpcServer
    arguments: ['@api.jsonrpc_serializer', '@api.jsonrpc_reflection']
    public: true

    api.form.login:
    class: App\Security\LoginFormAuthenticator

  • 2019-02-11 weaverryan

    Hey Roman Romanishyn!

    Hmm. Can you post your services.yaml code? And what version of Symfony are you using? I'm wondering if you're not using the new service auto-registration stuff and so you're using a different service id. Let me know :).

    Cheers!

  • 2019-02-11 Roman Romanishyn

    Thank you for reply. I double check everything and there is no typo.If running that command the same error occured in console. I'm trying define authenticator as a service and in this case everything looks good but --debug:container --show-private authenticator show only this:
    [0] maker.maker.make_authenticator
    [1] Symfony\Component\Security\Guard\GuardAuthenticatorHandler

  • 2019-02-11 weaverryan

    Hey Roman Romanishyn!

    Yep, I know this error! And you've already done the first thing correctly: double-check your namespace.

    Basically, this error means that, for some reason, the App\Security\LoginFormAuthenticator service does not exist. Check the spelling of the "namespace" inside that class as well as the class name to be sure. Also, what version of Symfony are you using? If everything is setup correctly with this service, you should see it when you run:


    php bin/console debug:container --show-private authenticator

    Let me know what you find out - it's most likely a small typo somewhere!

    Cheers!

  • 2019-02-10 Roman Romanishyn

    Hi! Ihave this error:
    The service "security.authentication.provider.guard.main" has a dependency on a non-existent service "App\Security\LoginFormAuthenticator".
    The namespace is correct

  • 2019-01-17 Diego Aguiar

    haha, no worries Aaron, sometimes we all speak too fast :p

  • 2019-01-15 Aaron Kincer

    Nevermind. Should have just watched the video and seen the instructions added. Anyone else wondering -- just keep going.

  • 2019-01-15 Aaron Kincer

    So if I understand correctly, when following the tutorial for learning purposes, we should choose "Empty authenticator" when running make:auth? Is there a simple instruction set to stay in line with the course at this point with the new wizard?

    Thanks!

  • 2018-11-27 Victor Bocharsky

    Hey Alex,

    Most probably so, "make:auth" command might become smarter in your version of MakerBundle :)

    Cheers!

  • 2018-11-26 Alex Finnarn

    Somehow "Under the main firewall, add a new guard key, a new authenticators key below that, and add one item in that array: App\Security\LoginFormAuthenticator:" was automagically done for me probably during "make:auth" here? https://github.com/symfony/... I did originally choose the "Login form authenticator" option but cancelled it before answering "The class name of the authenticator to create (e.g. AppCustomAuthenticator):".

  • 2018-10-29 weaverryan

    Hey @Bobby!

    Haha, like your name :). Yes, the new make:auth command awesomely now gives you the new option (2) - the login form authenticator. In this tutorial (before that option existed), we were effectively choosing option (1). And then we basically build (manually) a login form system. The final result of this tutorial actually looks quite similar to what you get if you choose option (2) (but we also wanted to build it manually so we could learn along the way).

    Anyways, I'm not sure I've answered your question yet :).

    > will you update this later in the future when building and rendering forms

    In our forms tutorial, we will *not* do any more work on the login form system. Both in this tutorial and in the updated generator, it does not use the form system. There is nothing wrong with using the form system for the login form, it's just such a simple form (and its validation is a bit different) that I prefer just using HTML forms for it. The only thing you need to remember (which we do in this tutorial and the generator also does it) is to add CSRF protection to your form.

    If I still haven't answered your question - let me know!

    Cheers!

  • 2018-10-24 Bobby strawberry and apples

    Hey Ryan it seems that the make:auth command has an upgrade now it give you 2 options
    1: empty authenticator
    2: login form authenticator
    if you choose option 2 you get the name the security/autheticator
    and your controller. will you update this later in the future when building and rendering forms?

  • 2018-09-17 weaverryan

    Hey Stéphane!

    Good question :). We change from AbstractGuardAuthenticator to AbstractFormLoginAuthenticator because, when you're building a login form, AbstractFormLoginAuthenticator requires you to do less work. If you look, AbstractFormLoginAuthenticator extends AbstractGuardAuthenticator, but it implements a few methods for you so that you don't have to. For example, when you're building a login form, we *know* that on failure, the user should be redirected to the login page. So, AbstractFormLoginAuthenticator implements the onAuthenticationError() method so that you don't have to.

    In other words, AbstractFormLoginAuthenticator is just a "helper" sub-class that allows you to do less work when you're building a login form :).

    Cheers!

  • 2018-09-15 Stéphane

    Hey Ryan,

    Thank for your reply. But why you don't use AbstractGuardAuthenticator ? It's about simplicity ?

  • 2018-09-14 weaverryan

    Hey Stéphane!

    Ah, typo! Yes, it should be AbstractGuardAuthenticator. It's not a Sf4 thing - it's just me saying the wrong class! You will see AbstractGuardAuthenticator in the video, even if I say something different in the script. Sorry for the confusion!

    Cheers!

  • 2018-09-13 Stéphane

    Hello,

    After make the command make:auth, in the LoginFormAuthenticator class extends AbstractGuardAuthenticator and no AbstractAuthenticator. This is due to the update of Sf4? Why you don't use it ?