Symfony2: Introduction to the Security Component part II

Posted on by Matthias Noback

Please note: I have revised this article to become part of the official documentation of the Security Component.

Authentication

When a request points to a secured area, and one of the listeners from the firewall map is able to extract the user's credentials from the current Request object, it should create a token, containing these credentials. The next thing the listener should do is ask the authentication manager to validate the given token, and return an authenticated token when the supplied credentials were found to be valid. The listener should then store the authenticated token in the security context:

use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

class SomeAuthenticationListener implements ListenerInterface
{
    /**
     * @var SecurityContextInterface
     */
    private $securityContext;

    /**
     * @var AuthenticationManagerInterface
     */
    private $authenticationManager;

    // string Uniquely identifies the secured area
    private $providerKey;

    // ...

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        $username = ...;
        $password = ...;

        $unauthenticatedToken = new UsernamePasswordToken(
            $username,
            $password,
            $this->providerKey);

        $authenticatedToken = $this
            ->authenticationManager
            ->authenticate($unauthenticatedToken);

        $this->securityContext->setToken($authenticatedToken);
    }
}

A token can be of any class, as long as it implements TokenInterface.

The authentication manager

The default authentication manager is an instance of AuthenticationProviderManager:

use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;

// instances of Symfony\Component\Security\Core\Authentication\AuthenticationProviderInterface
$providers = array(...);

$authenticationManager = new AuthenticationProviderManager($providers);

try {
    $authenticatedToken = $authenticationManager
        ->authenticate($unauthenticatedToken);
} catch (AuthenticationException $failed) {
    // authentication failed
}

The AuthenticationProviderManager, when instantiated, receives several authentication providers, each supporting a different type of token.

You may of course write your own authentication manager, it only has to implement AuthenticationManagerInterface.

Authentication providers

Each provider (since it implements AuthenticationProviderInterface) has a method supports() by which the AuthenticationProviderManager can determine if it supports the given token. If this is the case, the manager then calls the provider's method authenticate(). This method should return an authenticated token or throw an AuthenticationException (or any other exception extending it).

Authenticating users by their username and password

An authentication provider will attempt to authenticate a user based on the credentials he provided. Usually these are a username and a password. Most web applications store their user's username and a hash of the user's password combined with a randomly generated salt. This means that the average authentication would consist of fetching the salt and the hashed password from the user data storage, hash the password the user has just provided (e.g. using a login form) with the salt and compare both to determine if the given password is valid.

This functionality is offered by the DaoAuthenticationProvider. It fetches the user's data from a UserProvider, uses a PasswordEncoder to create a hash of the password and returns an authenticated token if the password was valid.

use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\User\UserChecker;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;

$userProvider = new InMemoryUserProvider(
    array('admin' => array(
        // password is "foo"
        'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
        'roles' => array('ROLE_ADMIN'),
    ),
);

// for some extra checks: is account enabled, locked, expired, etc.?
$userChecker = new UserChecker();

// an array of password encoders (see below)
$encoderFactory = new EncoderFactory(...);

$provider = new DaoAuthenticationProvider(
    $userProvider,
    $userChecker,
    'secured_area',
    $encoderFactory
);

$provider->authenticate($unauthenticatedToken);

The example above demonstrates the use of the "in-memory" user provider, but you may use any user provider, as long as it implements UserProviderInterface. It is also possible to let multiple user providers try to find the user's data, using the ChainUserProvider.

The password encoder factory

The DaoAuthenticationProvider uses an encoder factory to create a password encoder for a given type of user. This allows you to use different encoding strategies for different types of users. The default EncoderFactory receives an array of encoders:

use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;

$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);

$encoders = array(
    'Symfony\\Component\\Security\\Core\\User\\User' => $defaultEncoder,
    'Acme\\Entity\\LegacyUser' => $weakEncoder,
    ...
);

$encoderFactory = new EncoderFactory($encoders);

Each encoder should implement PasswordEncoderInterface or be an array with a class and an arguments key, which allows the encoder factory to construct the encoder only when it is needed.

Password encoders

When the getEncoder() method of the password encoder factory is called with the user object as its first argument, it will return an encoder of type PasswordEncoderInterface which should be used to encode this user's password:

// fetch a user of type Acme\Entity\LegacyUser
$user = ...

$encoder = $encoderFactory->getEncoder($user);

// will return $weakEncoder (see above)

$encodedPassword = $encoder->encodePassword($password, $user->getSalt());

// check if the password is valid:

$validPassword = $encoder->isPasswordValid(
    $user->getPassword(),
    $password,
    $user->getSalt());
PHP Security Symfony2 authentication documentation
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Kostia

$validPassword = $encoder->isPasswordValid(
$user->getPassword(),
$password, // I guess $encodedPassword should be used here
$user->getSalt());

Matthias Noback

Hi Kostia, no, $password is correct - it is the password as provided by the user (for instance by submitting the login form). This means it is in plain text. The password encoder will encode this password using the given salt and verify that it corresponds with the encoded password as found in the user object.

Kostia

But what's the purpose of $encodedPassword then? Thanks

Matthias Noback

My intention is not entirely clear, indeed. The code fragment shows two separate actions you can do with password encoders: encoding a password, and checking if the given password is valid.

Pawel

I would like get user from facebook, but I don't know how implements function handle(GetResponseEvent $event)

Matthias Noback

Hi Pawel, I think there would a bundle for that - you should check out http://knpbundles.com. Good luck!

Thanks alot! +1 for part 3!

So much clearer than the original symfony2 docs. Really helped me to grasp some of the concepts.

mTorres

Wow, thanks for this series, hope there will be part 3 :-D

Matthias Noback

Thanks, there will be!