Symfony
sponsored by SensioLabs
Menu
  • About
  • Documentation
  • Screencasts
  • Cloud
  • Certification
  • Community
  • Businesses
  • News
  • Download
  1. Home
  2. Documentation
  3. Cookbook
  4. Security
  5. How to Create a Custom Form Password Authenticator
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud
Search by Algolia

Table of Contents

  • The Password Authenticator
  • How it Works
    • 1) createToken
    • 2) supportsToken
    • 3) authenticateToken
  • Configuration

How to Create a Custom Form Password Authenticator

Edit this page

Warning: You are browsing the documentation for Symfony 2.4, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

How to Create a Custom Form Password Authenticator

Imagine you want to allow access to your website only between 2pm and 4pm UTC. Before Symfony 2.4, you had to create a custom token, factory, listener and provider. In this entry, you'll learn how to do this for a login form (i.e. where your user submits their username and password).

The Password Authenticator

2.4

The SimpleFormAuthenticatorInterface interface was introduced in Symfony 2.4.

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
// src/Acme/HelloBundle/Security/TimeAuthenticator.php
namespace Acme\HelloBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class TimeAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoderFactory;

    public function __construct(EncoderFactoryInterface $encoderFactory)
    {
        $this->encoderFactory = $encoderFactory;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        try {
            $user = $userProvider->loadUserByUsername($token->getUsername());
        } catch (UsernameNotFoundException $e) {
            throw new AuthenticationException('Invalid username or password');
        }

        $encoder = $this->encoderFactory->getEncoder($user);
        $passwordValid = $encoder->isPasswordValid(
            $user->getPassword(),
            $token->getCredentials(),
            $user->getSalt()
        );

        if ($passwordValid) {
            $currentHour = date('G');
            if ($currentHour < 14 || $currentHour > 16) {
                throw new AuthenticationException(
                    'You can only log in between 2 and 4!',
                    100
                );
            }

            return new UsernamePasswordToken(
                $user,
                $user->getPassword(),
                $providerKey,
                $user->getRoles()
            );
        }

        throw new AuthenticationException('Invalid username or password');
    }

    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, an encoder is needed to check the password's validity:

1
2
3
4
5
6
$encoder = $this->encoderFactory->getEncoder($user);
$passwordValid = $encoder->isPasswordValid(
    $user->getPassword(),
    $token->getCredentials(),
    $user->getSalt()
);

This is a service that is already available in Symfony and the password algorithm 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, configure your TimeAuthenticator as a service:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
# app/config/config.yml
services:
    # ...

    time_authenticator:
        class:     Acme\HelloBundle\Security\TimeAuthenticator
        arguments: ["@security.encoder_factory"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <!-- ... -->

        <service id="time_authenticator"
            class="Acme\HelloBundle\Security\TimeAuthenticator"
        >
            <argument type="service" id="security.encoder_factory" />
        </service>
    </services>
</container>
1
2
3
4
5
6
7
8
9
10
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

// ...

$container->setDefinition('time_authenticator', new Definition(
    'Acme\HelloBundle\Security\TimeAuthenticator',
    array(new Reference('security.encoder_factory'))
));

Then, activate it in the firewalls section of the security configuration using the simple_form key:

  • YAML
  • XML
  • PHP
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: time_authenticator
                check_path:    login_check
                login_path:    login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
    <config>
        <!-- ... -->

        <firewall name="secured_area"
            pattern="^/admin"
            >
            <simple-form authenticator="time_authenticator"
                check-path="login_check"
                login-path="login"
            />
        </firewall>
    </config>
</srv:container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/config/security.php

// ..

$container->loadFromExtension('security', array(
    'firewalls' => array(
        'secured_area'    => array(
            'pattern'     => '^/admin',
            'simple_form' => array(
                'provider'      => ...,
                'authenticator' => 'time_authenticator',
                'check_path'    => 'login_check',
                'login_path'    => 'login',
            ),
        ),
    ),
));

The 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 SecurityBundle Configuration ("security").

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 your Form Login.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
We stand with Ukraine.
Version:
Become certified from home

Become certified from home

Peruse our complete Symfony & PHP solutions catalog for your web development needs.

Peruse our complete Symfony & PHP solutions catalog for your web development needs.

↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

Avatar of Tyson Andre, a Symfony contributor

Thanks Tyson Andre for being a Symfony contributor

10 commits • 103 lines changed

View all contributors that help us make Symfony

Become a Symfony contributor

Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

Learn how to contribute

Symfony™ is a trademark of Symfony SAS. All rights reserved.

  • What is Symfony?
    • Symfony at a Glance
    • Symfony Components
    • Case Studies
    • Symfony Releases
    • Security Policy
    • Logo & Screenshots
    • Trademark & Licenses
    • symfony1 Legacy
  • Learn Symfony
    • Symfony Docs
    • Symfony Book
    • Reference
    • Bundles
    • Best Practices
    • Training
    • eLearning Platform
    • Certification
  • Screencasts
    • Learn Symfony
    • Learn PHP
    • Learn JavaScript
    • Learn Drupal
    • Learn RESTful APIs
  • Community
    • SymfonyConnect
    • Support
    • How to be Involved
    • Code of Conduct
    • Events & Meetups
    • Projects using Symfony
    • Downloads Stats
    • Contributors
    • Backers
  • Blog
    • Events & Meetups
    • A week of symfony
    • Case studies
    • Cloud
    • Community
    • Conferences
    • Diversity
    • Documentation
    • Living on the edge
    • Releases
    • Security Advisories
    • SymfonyInsight
    • Twig
    • SensioLabs
  • Services
    • SensioLabs services
    • Train developers
    • Manage your project quality
    • Improve your project performance
    • Host Symfony projects
    Deployed on
Follow Symfony
Search by Algolia