Skip to content
Logo Theodo

Guard: Creating a Simple Authentication System for Symfony

Antoine Kahn5 min read

Building a custom authentication system for Symfony can get atrocious.
You can get a glimpse of that here.
You have to deal with multiple classes, connect them to each other, and hope for the best.
It is hard to customize and never fun to work with.

Since Symfony 2.8, to simplify the customization of the authentication process, Guard has been introduced.
With Guard, you will not have any struggle building your own authentication system.
It does not redesign the existing authentication system included in Symfony, it plugs itself onto it, making your life easier.
Let’s explain how it works, and how you can use it!

Creating an Authenticator

With Guard, every step of the authentication process is handled by only one class: an Authenticator.
This class will have to implement the provided GuardAuthenticatorInterface.
This interface comes with seven simple methods:

start(Request $request, AuthenticationException $authException = null)

This gets called when the user tries to access a resource that requires authentication, but no authentication information was found in the request.
Its job is to inform the client that he has to send those authentication details.
This method is a bit different from the others, since it comes from AuthenticationEntryPointInterface, which is extended by GuardAuthenticatorInterface.
For example, you could redirect him to the login page:

/**
 * @var \Symfony\Component\Routing\RouterInterface
 */
private $router;

public function start(Request $request, AuthenticationException $authException = null)
{
  $url = $this->router->generate('login');
  return new RedirectResponse($url);
}

getCredentials(Request $request)

This method will get called on every request that requires an authentication.
Its job is to read the authentication information contained in the request, and return it.
You can return what you want! The only purpose what you return is to get used in the getUser() and checkCredentials() methods.
If this method returns null, authentication will fail.
So if the endpoint requires an authentication, the method start() will get called.
If not, the authentication gets skipped, the user is the famous "anon".
If you return a non null value, the method getUser() will get called.
Two examples:

// for an API
public function getCredentials(Request $request)
{
  return $request->headers->get('X-API-TOKEN');
}

// for a form login
public function getCredentials(Request $request)
{
  return array(
    'username' => $request->request->get('_username'),
    'password' => $request->request->get('_password'),
  );
}

getUser($credentials, UserProviderInterface $userProvider)

After you’ve gotten the credentials, you will try to get the User associated with those credentials.
The value of the credentials is passed to getUser() as the $credentials argument.
The job of this method is to return an object implementing UserInterface.
If it does, the next step of the authentication will be called: checkCredentials().
Else, the authentication will fail and the method onAuthenticationFailure() will get called.
An example:

# for an API
public function getUser($credentials, UserProviderInterface $userProvider)
{
  $user = $this->em->getRepository('AppBundle:User')
      ->findOneBy(array('apiToken' => $credentials));

  return $user;
}

checkCredentials($credentials, UserInterface $user)

The job of this method is to check if the credentials of the previously returned User are correct.
This method can do two things.
If it returns true, the user will be authenticated, and the method onAuthenticationSuccess() will be called.
If does not, the authentication fails and the method onAuthenticationFailure() is called.
Even if it works without, throwing any kind of AuthenticationException lets you explicit what went wrong.
An example with a password:

public function checkCredentials($credentials, UserInterface $user)
{
  if ($user->getPassword() === $credentials['password']) {
    return true;
  }

  throw new MyCustomAuthenticationException('The credentials are wrong!');
}

onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)

This method is called when the user is successfully authenticated.
It can return null, in which case the request continues to process as expected, or return a Reponse object, in which case this Response will be transfered to the user.
For example, you can redirect your users to the homepage:

/**
 * @var \Symfony\Component\Routing\RouterInterface
 */
private $router;

public function __construct(RouterInterface $router)
{
  $this->router = $router;
}

# ...

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
  $url = $this->router->generate('homepage');

  return new RedirectResponse($url);
}

onAuthenticationFailure(Request $request, AuthenticationException $exception)

This method is called when the authentication fails.
Its job is to return a Reponse object that will be sent to the client.
You will know what went wrong in the process with the $exception parameter.
For example, you can return a custom JSON response:

public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
  return new JsonResponse(array('message' => $exception->getMessageKey()), Response::HTTP_FORBIDDEN);
}

supportsRememberMe()

Return true with this method if you want the remember me functionality to be active, false otherwise.
It will still requires the activation of the remember_me under your firewall to work.
A very simple example:

public function supportsRememberMe()
{
  return false;
}

registering your Authenticator

You have built a very nice Authenticator, but how can you use it in your application?
First, register in your security.yml file, under the firewall sections, that you will be using Guard.
For example:

firewalls:
    secured_area:
        anonymous: ~
        logout:
            path:   /logout
              target: /
        guard:
            authenticators:
                - my_custom_authenticator

Then, register your Authenticator as a service, for example in your service.yml:

services:
    my_custom_authenticator:
        class: AppBundle\Security\Authenticator
        arguments: ["@router"]

You can even specify multiple authenticators like so:

guard:
    authenticators:
        - my_custom_authenticator
        - my_facebook_authenticator
    entry_point: my_custom_authenticator

It this case, you will have to tell your application which authenticator will be your entry point, that is which start() method will be called when an anonymous user tries to access a resource requiring authentication.

And that’s it! You can now build your own custom authentication process, and only by implementing very simple methods.
You can easily use Guard to allow an authentication via Facebook, Google+, Github or whatever application you want.
Have a nice time building your Symfony authentication systems!

Liked this article?