If you have read the chapter on Security, you understand the
distinction Symfony2 makes between authentication and authorization in the
implementation of security. This chapter discusses the core classes involved
in the authentication process, and how to implement a custom authentication
provider. Because authentication and authorization are separate concepts,
this extension will be user-provider agnostic, and will function with your
application's user providers, may they be based in memory, a database, or
wherever else you choose to store them.

The following chapter demonstrates how to create a custom authentication
provider for WSSE authentication. The security protocol for WSSE provides
several security benefits:

Username / Password encryption

Safe guarding against replay attacks

No web server configuration required

WSSE is very useful for the securing of web services, may they be SOAP or
REST.

There is plenty of great documentation on WSSE, but this article will
focus not on the security protocol, but rather the manner in which a custom
protocol can be added to your Symfony2 application. The basis of WSSE is
that a request header is checked for encrypted credentials, verified using
a timestamp and nonce, and authenticated for the requested user using a
password digest.

Note

WSSE also supports application key validation, which is useful for web
services, but is outside the scope of this chapter.

The role of the token in the Symfony2 security context is an important one.
A token represents the user authentication data present in the request. Once
a request is authenticated, the token retains the user's data, and delivers
this data across the security context. First, you'll create your token class.
This will allow the passing of all relevant information to your authentication
provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

// src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.phpnamespaceAcme\DemoBundle\Security\Authentication\Token;useSymfony\Component\Security\Core\Authentication\Token\AbstractToken;classWsseUserTokenextendsAbstractToken{public$created;public$digest;public$nonce;publicfunction__construct(array$roles=array()){parent::__construct($roles);// If the user has roles, consider it authenticated$this->setAuthenticated(count($roles)>0);}publicfunctiongetCredentials(){return'';}}

Note

The WsseUserToken class extends the security component's
AbstractToken
class, which provides basic token functionality. Implement the
TokenInterface
on any class to use as a token.

Next, you need a listener to listen on the security context. The listener
is responsible for fielding requests to the firewall and calling the authentication
provider. A listener must be an instance of
ListenerInterface.
A security listener should handle the
GetResponseEvent event, and
set an authenticated token in the security context if successful.

This listener checks the request for the expected X-WSSE header, matches
the value returned for the expected WSSE information, creates a token using
that information, and passes the token on to the authentication manager. If
the proper information is not provided, or the authentication manager throws
an AuthenticationException,
a 403 Response is returned.

Note

A class not used above, the
AbstractAuthenticationListener
class, is a very useful base class which provides commonly needed functionality
for security extensions. This includes maintaining the token in the session,
providing success / failure handlers, login form urls, and more. As WSSE
does not require maintaining authentication sessions or login forms, it
won't be used for this example.

The authentication provider will do the verification of the WsseUserToken.
Namely, the provider will verify the Created header value is valid within
five minutes, the Nonce header value is unique within five minutes, and
the PasswordDigest header value matches with the user's password.

// src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.phpnamespaceAcme\DemoBundle\Security\Authentication\Provider;useSymfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;useSymfony\Component\Security\Core\User\UserProviderInterface;useSymfony\Component\Security\Core\Exception\AuthenticationException;useSymfony\Component\Security\Core\Exception\NonceExpiredException;useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;useAcme\DemoBundle\Security\Authentication\Token\WsseUserToken;classWsseProviderimplementsAuthenticationProviderInterface{private$userProvider;private$cacheDir;publicfunction__construct(UserProviderInterface$userProvider,$cacheDir){$this->userProvider=$userProvider;$this->cacheDir=$cacheDir;}publicfunctionauthenticate(TokenInterface$token){$user=$this->userProvider->loadUserByUsername($token->getUsername());if($user&&$this->validateDigest($token->digest,$token->nonce,$token->created,$user->getPassword())){$authenticatedToken=newWsseUserToken($user->getRoles());$authenticatedToken->setUser($user);return$authenticatedToken;}thrownewAuthenticationException('The WSSE authentication failed.');}protectedfunctionvalidateDigest($digest,$nonce,$created,$secret){// Check created time is not in the futureif(strtotime($created)>time()){returnfalse;}// Expire timestamp after 5 minutesif(time()-strtotime($created)>300){returnfalse;}// Validate nonce is unique within 5 minutesif(file_exists($this->cacheDir.'/'.$nonce)&&file_get_contents($this->cacheDir.'/'.$nonce)+300>time()){thrownewNonceExpiredException('Previously used nonce detected');}file_put_contents($this->cacheDir.'/'.$nonce,time());// Validate Secret$expected=base64_encode(sha1(base64_decode($nonce).$created.$secret,true));return$digest===$expected;}publicfunctionsupports(TokenInterface$token){return$tokeninstanceofWsseUserToken;}}

Note

The AuthenticationProviderInterface
requires an authenticate method on the user token, and a supports
method, which tells the authentication manager whether or not to use this
provider for the given token. In the case of multiple providers, the
authentication manager will then move to the next provider in the list.

You have created a custom token, custom listener, and custom provider. Now
you need to tie them all together. How do you make your provider available
to your security configuration? The answer is by using a factory. A factory
is where you hook into the security component, telling it the name of your
provider and any configuration options available for it. First, you must
create a class which implements
SecurityFactoryInterface.

create method, which adds the listener and authentication provider
to the DI container for the appropriate security context;

getPosition method, which must be of type pre_auth, form, http,
and remember_me and defines the position at which the provider is called;

getKey method which defines the configuration key used to reference
the provider;

addConfiguration method, which is used to define the configuration
options underneath the configuration key in your security configuration.
Setting configuration options are explained later in this chapter.

Note

A class not used in this example,
AbstractFactory,
is a very useful base class which provides commonly needed functionality
for security factories. It may be useful when defining an authentication
provider of a different type.

Now that you have created a factory class, the wsse key can be used as
a firewall in your security configuration.

Note

You may be wondering "why do you need a special factory class to add listeners
and providers to the dependency injection container?". This is a very
good question. The reason is you can use your firewall multiple times,
to secure multiple parts of your application. Because of this, each
time your firewall is used, a new service is created in the DI container.
The factory is what creates these new services.

It's time to see your authentication provider in action. You will need to
do a few things in order to make this work. The first thing is to add the
services above to the DI container. Your factory class above makes reference
to service ids that do not exist yet: wsse.security.authentication.provider and
wsse.security.authentication.listener. It's time to define those services.

Now that your services are defined, tell your security context about your
factory. Factories must be included in an individual configuration file,
at the time of this writing. So, start first by creating the file with the
factory service, tagged as security.listener.factory:

You can add custom options under the wsse key in your security configuration.
For instance, the time allowed before expiring the Created header item,
by default, is 5 minutes. Make this configurable, so different firewalls
can have different timeout lengths.

You will first need to edit WsseFactory and define the new option in
the addConfiguration method.

Now, in the create method of the factory, the $config argument will
contain a 'lifetime' key, set to 5 minutes (300 seconds) unless otherwise
set in the configuration. Pass this argument to your authentication provider
in order to put it to use.

You'll also need to add a third argument to the wsse.security.authentication.provider
service configuration, which can be blank, but will be filled in with
the lifetime in the factory. The WsseProvider class will also now
need to accept a third constructor argument - the lifetime - which it
should use instead of the hard-coded 300 seconds. These two steps are
not shown here.

The lifetime of each wsse request is now configurable, and can be
set to any desirable value per firewall.