How to Create a Custom Form Password Authenticator
Warning: You are browsing the documentation for Symfony 3.x, which is no longer maintained.
Read the updated version of this page for Symfony 7.3 (the current stable version).
Tip
Check out How to Create a Custom Authentication System with Guard for a simpler and more flexible way to accomplish custom authentication tasks like this.
Imagine you want to allow access to your website only between 2pm and 4pm UTC. In this article, you'll learn how to do this for a login form (i.e. where your user submits their username and password).
The Password Authenticator
First, create a new class that implements SimpleFormAuthenticatorInterface. Eventually, this will allow you to create custom logic for authenticating the user:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
// src/AppBundle/Security/TimeAuthenticator.php
namespace AppBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimpleFormAuthenticatorInterface;
class TimeAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoder;
    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        try {
            $user = $userProvider->loadUserByUsername($token->getUsername());
        } catch (UsernameNotFoundException $exception) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new CustomUserMessageAuthenticationException('Invalid username or password');
        }
        $currentUser = $token->getUser();
        if ($currentUser instanceof UserInterface) {
            if ($currentUser->getPassword() !== $user->getPassword()) {
                throw new BadCredentialsException('The credentials were changed from another session.');
            }
        } else {
            if ('' === ($givenPassword = $token->getCredentials())) {
                throw new BadCredentialsException('The given password cannot be empty.');
            }
            if (!$this->encoder->isPasswordValid($user, $givenPassword)) {
                throw new BadCredentialsException('The given password is invalid.');
            }
        }
        $currentHour = date('G');
        if ($currentHour < 14 || $currentHour > 16) {
            // CAUTION: this message will be returned to the client
            // (so don't put any un-trusted messages / error strings here)
            throw new CustomUserMessageAuthenticationException(
                'You can only log in between 2 and 4!',
                [], // Message Data
                412 // HTTP 412 Precondition Failed
            );
        }
        return new UsernamePasswordToken(
            $user,
            $user->getPassword(),
            $providerKey,
            $user->getRoles()
        );
    }
    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof UsernamePasswordToken
            && $token->getProviderKey() === $providerKey;
    }
    public function createToken(Request $request, $username, $password, $providerKey)
    {
        return new UsernamePasswordToken($username, $password, $providerKey);
    }
}How it Works
Great! Now you just need to setup some How to Create a Custom Form Password Authenticator. But first, you can find out more about what each method in this class does.
1) createToken()
When Symfony begins handling a request, createToken() is called, where
you create a TokenInterface
object that contains whatever information you need in authenticateToken()
to authenticate the user (e.g. the username and password).
Whatever token object you create here will be passed to you later in authenticateToken().
2) supportsToken()
After Symfony calls createToken(), it will then call supportsToken()
on your class (and any other authentication listeners) to figure out who should
handle the token. This is just a way to allow several authentication mechanisms
to be used for the same firewall (that way, you can for instance first try
to authenticate the user via a certificate or an API key and fall back to
a form login).
Mostly, you just need to make sure that this method returns true for a
token that has been created by createToken(). Your logic should probably
look exactly like this example.
3) authenticateToken()
If supportsToken() returns true, Symfony will now call authenticateToken().
Your job here is to check that the token is allowed to log in by first
getting the User object via the user provider and then, by checking the password
and the current time.
Note
The "flow" of how you get the User object and determine whether or not
the token is valid (e.g. checking the password), may vary based on your
requirements.
Ultimately, your job is to return a new token object that is "authenticated"
(i.e. it has at least 1 role set on it) and which has the User object
inside of it.
Inside this method, the password encoder is needed to check the password's validity:
1
$isPasswordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());This is a service that is already available in Symfony and it uses the password algorithm
that is configured in the security configuration (e.g. security.yml) under
the encoders key. Below, you'll see how to inject that into the TimeAuthenticator.
Configuration
Now, make sure your TimeAuthenticator is registered as a service. If you're
using the default services.yml configuration,
that happens automatically.
Finally, activate the service in the firewalls section of the security configuration
using the simple_form key:
1 2 3 4 5 6 7 8 9 10 11 12
# app/config/security.yml
security:
    # ...
    firewalls:
        secured_area:
            pattern: ^/admin
            # ...
            simple_form:
                authenticator: AppBundle\Security\TimeAuthenticator
                check_path:    login_check
                login_path:    loginThe simple_form key has the same options as the normal form_login
option, but with the additional authenticator key that points to the
new service. For details, see Security Configuration Reference (SecurityBundle).
If creating a login form in general is new to you or you don't understand
the check_path or login_path options, see How to Customize Redirect After Form Login.