New in Symfony 2.4: Customize the Security Features with ease

So, you've heard that using the Symfony2 security layer was complex? I tend to
both agree and disagree. On the one hand, if your needs are "standard" (form
authentication with users stored in a database, HTTP basic authentication,
...), setting up the security is really as easy as configuring some options.

But on the other hand, if you want a custom authentication/authorization/user
provider system, things are getting a tad more complex as you need to
understand all the concepts and how you need to wire up everything. As of
Symfony 2.4, this process has been made easier thanks to the introduction of
some easier way to customize the security layer without the need to create a
bunch of classes. In this post, I'm going to describe how to code some common
features.

Before starting to dig into the new customization features, let's simplify
things a bit by storing our user credentials in a flat file. Of course, most
of the time, the user credentials are stored in a database; and Symfony
provides built-in integration for Doctrine and Propel.

To keep the example simple, let's use a simple JSON file:

The usernames are the keys and the values are the passwords, encoded with the
md5 function to keep it simple.

Creating a user provider is as simple as implementing
Symfony\Component\Security\Core\User\UserProviderInterface:

useSymfony\Component\Security\Core\User\User;useSymfony\Component\Security\Core\User\UserProviderInterface;useSymfony\Component\Security\Core\User\UserInterface;useSymfony\Component\Security\Core\Exception\UsernameNotFoundException;useSymfony\Component\Security\Core\Exception\UnsupportedUserException;classJsonUserProviderimplementsUserProviderInterface{protected$users;publicfunction__construct(){$this->users=json_decode(file_get_contents('/path/to/users.json'),true);}publicfunctionloadUserByUsername($username){if(isset($this->users[$username])){returnnewUser($username,$this->users[$username],array('ROLE_USER'));}thrownewUsernameNotFoundException(sprintf('Username "%s" does not exist.',$username));}publicfunctionrefreshUser(UserInterface$user){if(!$userinstanceofUser){thrownewUnsupportedUserException(sprintf('Instances of "%s" are not supported.',get_class($user)));}return$this->loadUserByUsername($user->getUsername());}publicfunctionsupportsClass($class){return'Symfony\Component\Security\Core\User\User'===$class;}}

We are using the built-in User class to represent our users. Using this
provider in your configuration is straightforward:

Now, let's say that we want the users to be able to access our website only
between 2 and 4 o-clock in the afternoon (for the UTC timezone). How would you
do that? Obviously, there is no built-in configuration setting for such a
requirement. So, we need to customize the way the user is authenticated.

And here comes the interesting part. Instead of creating a custom token,
factory, listener, and provider, let's use the new
Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface
interface instead (to keep thing simple, we are extending the user provider
here):

useSymfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;useSymfony\Component\Security\Core\Exception\AuthenticationException;useSymfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;useSymfony\Component\Security\Core\User\UserProviderInterface;useSymfony\Component\Security\Core\Exception\UsernameNotFoundException;useSymfony\Component\Security\Core\Encoder\EncoderFactoryInterface;useSymfony\Component\HttpFoundation\Request;classTimeAuthenticatorextendsJsonUserProviderimplementsSimpleFormAuthenticatorInterface{private$encoderFactory;publicfunction__construct(EncoderFactoryInterface$encoderFactory){$this->encoderFactory=$encoderFactory;}publicfunctioncreateToken(Request$request,$username,$password,$providerKey){returnnewUsernamePasswordToken($username,$password,$providerKey);}publicfunctionauthenticateToken(TokenInterface$token,UserProviderInterface$userProvider,$providerKey){try{$user=$userProvider->loadUserByUsername($token->getUsername());}catch(UsernameNotFoundException$e){thrownewAuthenticationException('Invalid username or password');}$passwordValid=$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(),$token->getCredentials(),$user->getSalt());if($passwordValid){$currentHour=date('G');if($currentHour<14||$currentHour>16){thrownewAuthenticationException('You can only log in between 2 and 4!',100);}returnnewUsernamePasswordToken($user,'bar',$providerKey,$user->getRoles());}thrownewAuthenticationException('Invalid username or password');}publicfunctionsupportsToken(TokenInterface$token,$providerKey){return$tokeninstanceofUsernamePasswordToken&&$token->getProviderKey()===$providerKey;}}

There are a lot of things going on:

createToken() creates a Token that will be used to authenticate the
user;

authenticateToken() checks that the Token is allowed to log in by
first getting the User via the user provider and then, by checking the
password and the current time (a Token with roles is authenticated);

supportsToken() is just a way to allow several authentication mechanism
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 fallback to a
form login);

An encoder is needed to check the user password validity; this is a service
provided by default:

Now, how do we wire this class into our configuration? Because we have
extended
Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface,
replace form-login by simple-form, and set the authenticator
option to the time_authenticator service:

After the authentication mechanism, you have the chance to tweak the default
behavior by adding some methods to you authenticator class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

useSymfony\Component\Security\Core\Exception\AuthenticationException;useSymfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;useSymfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\Response;classCustomTimeAuthenticatorextendsTimeAuthenticatorimplementsAuthenticationFailureHandlerInterface,AuthenticationSuccessHandlerInterface{publicfunctiononAuthenticationFailure(Request$request,AuthenticationException$exception){error_log('You are out!');}publicfunctiononAuthenticationSuccess(Request$request,TokenInterface$token){error_log(sprintf('Yep, you are in "%s"!',$token->getUsername()));}}

Here, we are using the error_log() function to log some information. But
you can also bypass the default behavior altogether by returning a
Response instance:

1
2
3
4
5
6

publicfunctiononAuthenticationFailure(Request$request,AuthenticationException$exception){if($exception->getCode()){returnnewResponse('Not the right time to log in, come back later.');}}

Nowadays, it's quite usual to authenticate the user via an API key (when
developing a web service for instance). The API key is provided for every
request and is passed as a query string parameter or via a HTTP header.

Let's use the same JSON file for our API keys, but values are now the user API
key. Authenticating a user based on the Request information should be done via
a pre-authentication mechanism. The new
Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface
class allows to implement such a scheme really easily:

useSymfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;useSymfony\Component\Security\Core\Exception\AuthenticationException;useSymfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\Security\Core\User\User;useSymfony\Component\Security\Core\User\UserProviderInterface;useSymfony\Component\Security\Core\Exception\UsernameNotFoundException;useSymfony\Component\Security\Core\Exception\BadCredentialsException;classTimeAuthenticatorextendsJsonUserProviderimplementsSimplePreAuthenticatorInterface{protected$apikeys;publicfunction__construct(){parent::__construct();$this->apikeys=array_flip($this->users);}publicfunctioncreateToken(Request$request,$providerKey){if(!$request->query->has('apikey')){thrownewBadCredentialsException('No API key found');}returnnewPreAuthenticatedToken('anon.',$request->query->get('apikey'),$providerKey);}publicfunctionauthenticateToken(TokenInterface$token,UserProviderInterface$userProvider,$providerKey){$currentHour=date('G');if($currentHour<14||$currentHour>16){thrownewAuthenticationException('You can only log in between 2 and 4!',100);}$apikey=$token->getCredentials();if(!isset($this->apikeys[$apikey])){thrownewAuthenticationException(sprintf('API Key "%s" does not exist.',$apikey));}$user=newUser($this->apikeys[$apikey],$apikey,array('ROLE_USER'));returnnewPreAuthenticatedToken($user,$token->getCredentials(),$providerKey,$user->getRoles());}publicfunctionsupportsToken(TokenInterface$token,$providerKey){return$tokeninstanceofPreAuthenticatedToken&&$token->getProviderKey()===$providerKey;}}

As you can see, the class looks almost the same as the one based on a form,
except that we are using a
Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken
token class.

To access a resource protected by such an authenticator, you need to add an
apikey parameter to the query string, like in
http://example.com/admin/foo?apikey=37b51d194a7513e45b56f6524f2d51f2.

The configuration is also straightforward (replace simple-form with
simple-preauth):

If you application is able to return both an HTML and a JSON/XML
representation of the resources, it might be a good idea to support both an
API-key based authentication mechanism (for programmatic access) and a regular
authentication login form (for browsers).

I hope that this new way to customize the Security features of Symfony will
help lower the barrier of entry to new developers. This new feature is still
experimental and might change in until 2.4 is released, based on your
feedback. So, try to use it and tell us what your think about it.

```php
return new UsernamePasswordToken($user, 'bar', $providerKey, $user->getRoles());
```
you assigned 'bar' as password, i guess is just a fill in, but why do we have to do this? in which way it impacts the handling of the token later on? or should it be $user->getPassword()?