knpuniversity/guard-bundle
KnpUGuardBundle is a Symfony bundle that integrates the Guard authentication component, making it easy to build custom authenticators for form login, API tokens, social sign-in, and other authentication flows with a flexible, well-documented interface.
knpuniversity/guard-bundle backports Symfony's Guard authentication system to Symfony 2.3–2.7. It is archived (last release 2015-06-28) and is only appropriate for legacy projects still on Symfony < 2.8. If you are on 2.8+/3.x, use the built-in symfony/security-guard component instead; if you are on 5.3+, migrate to the new AuthenticatorInterface. Everything below applies equally to the bundle and to the built-in component, since the API is identical.
Minimal steps:
composer.json require "knpuniversity/guard-bundle": "~1.0" (the constraint should mirror whatever your Symfony version allows), then add it to app/AppKernel.php:$bundles = array(
// ...
new KnpU\GuardBundle\KnpUGuardBundle(),
);
// src/AppBundle/Security/FormLoginAuthenticator.php
use KnpU\GuardBundle\Guard\GuardAuthenticatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class FormLoginAuthenticator implements GuardAuthenticatorInterface
{
private $encoderFactory;
private $router;
public function __construct($encoderFactory, $router)
{
$this->encoderFactory = $encoderFactory;
$this->router = $router;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() !== '/login_check' || !$request->isMethod('POST')) {
return null; // skip this authenticator
}
return array(
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUsername($credentials['username']);
}
public function checkCredentials($credentials, $user)
{
$encoder = $this->encoderFactory->getEncoder($user);
return $encoder->isPasswordValid($user->getPassword(), $credentials['password'], $user->getSalt());
}
public function createAuthenticatedToken($user, $providerKey)
{
// Not needed on Symfony >= 2.6; the bundle falls back to a sensible default.
return null;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->router->generate('dashboard'));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new RedirectResponse($this->router->generate('login') . '?error=1');
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->router->generate('login'));
}
public function supportsRememberMe()
{
return true;
}
}
# app/config/security.yml
security:
providers:
db_provider:
entity: { class: AppBundle:User, property: username }
firewalls:
main:
pattern: ^/
anonymous: ~
guard:
provider: db_provider
authenticators:
- app.form_login_authenticator
entry_point: app.form_login_authenticator
getCredentials/start.Where to look first:
GuardAuthenticatorInterface in the bundle — that is the contract, and the eight methods map 1:1 to the lifecycle.Resources/doc examples (if present in your tag).One authenticator per auth mechanism, not per firewall. A form login, an API token, and an OAuth callback should be three classes, each returning null from getCredentials() when the request isn't theirs. Multiple authenticators on a single firewall is an explicitly supported pattern:
firewalls:
main:
pattern: ^/
guard:
provider: db_provider
authenticators:
- app.api_token_authenticator
- app.form_login_authenticator
entry_point: app.form_login_authenticator
Ordering matters only for the entry_point/challenge behavior and for remember-me token handling; credential extraction from each authenticator is attempted in order until one returns non-null.
Stateless API authenticator. The most common real-world use, and where Guard beats the old listener approach:
class ApiTokenAuthenticator implements GuardAuthenticatorInterface
{
public function getCredentials(Request $request)
{
return $request->headers->get('X-AUTH-TOKEN');
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUsername($credentials); // token == username in toy setups
}
public function checkCredentials($credentials, $user)
{
return true; // token validated by the provider above
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null; // continue the request
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new JsonResponse(array('message' => 'Authentication required'), 401);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new JsonResponse(array('message' => $exception->getMessageKey()), 401);
}
public function supportsRememberMe() { return false; }
}
Put this on a firewall with stateless: true so no session is started.
OAuth / social login (hybrid flow). start() is the "challenge" — for social login it redirects the browser to the provider's consent screen. getCredentials() then reads ?code= on the callback route, and getUser() exchanges the code for a profile and returns a user object (or creates one):
public function start(Request $request, AuthenticationException $e = null)
{
return new RedirectResponse($this->oauth->getAuthorizationUrl());
}
public function getCredentials(Request $request)
{
return $request->query->get('code'); // null on every other request
}
Base class to cut the boilerplate. In most apps you will write 3–4 authenticators and repeat createAuthenticatedToken, supportsRememberMe, and the onAuthenticationFailure shape every time. Create your own AbstractAppAuthenticator that implements those defaults and let concrete classes implement only getCredentials, getUser, checkCredentials, onAuthenticationSuccess, start. (Symfony 2.8's component ships AbstractGuardAuthenticator for the same purpose; the bundle's version predates it.)
Return null from onAuthenticationSuccess to continue the request. For an API authenticator, returning a Response short-circuits the controller — useful only when you want to terminate (e.g., redirect after form login). Returning null lets the normal controller run with the token in the security context.
Integrate via the firewall, not the event system. Resist the temptation to also register old-style listeners; Guard owns authentication for the firewall it is attached to. Things like "remember me" (supportsRememberMe()), CSRF (handle it yourself in the form or via _csrf_token check in getCredentials), and logout stay configured in security.yml as normal.
Testing. Because an authenticator is a plain service with constructor dependencies, unit-test it directly: mock Request, a UserProviderInterface, and the encoder factory. The awkward one is checkCredentials — inject the encoder factory rather than hard-coding security.encoder_factory lookups so you can stub it.
The bundle is dead weight on Symfony 2.8+. The Guard system was merged into symfony/security-guard; keeping the bundle means two copies of GuardAuthenticatorInterface and an autoload conflict or a confusing "which namespace?" moment. On upgrade, delete the bundle, drop the new KnpUGuardBundle() line, and change your use statements to Symfony\Component\Security\Guard\GuardAuthenticatorInterface. If a class implements both namespaces, remove the bundle one. Plan this migration before you invest heavily.
checkCredentials() is mandatory and does not delegate to the provider. The old UserAuthenticationProvider checked passwords for you. Guard does not. You must fetch the encoder via security.encoder_factory and call isPasswordValid() yourself. Forgetting this results in every password being accepted.
getUser() returning null is an error, not a "not found". Throw UsernameNotFoundException (or a subclass of AuthenticationException) so onAuthenticationFailure receives a meaningful exception. Returning null yields a generic BadCredentialsException.
getCredentials() returning null skips the authenticator entirely. This is the mechanism that makes multi-authenticator firewalls work, and it is also the most common source of "my authenticator never runs." Log at the top of getCredentials() while debugging.
onAuthenticationFailure() must return a Response. Returning null throws a RuntimeException from the Guard handler. onAuthenticationSuccess() and createAuthenticatedToken() may return null.
start() vs entry_point. start() is only called when the firewall needs to challenge an anonymous user (and no previous exception is stored). With several authenticators, entry_point: <service id> selects which one's start() runs. If you omit entry_point and have more than one authenticator, Symfony raises an error at container compile time.
Default redirect behavior is gone. With classic form login, Symfony redirected to default_target_path automatically. With Guard, if onAuthenticationSuccess() returns null, the request simply continues — on a /login_check route with no controller that produces a confusing blank page or 404. Either return a RedirectResponse yourself, or use the target-path session logic ($request->getSession()->get('_security.main.target_path')) explicitly.
CSRF is not handled for you. Add _csrf_token to your login form and validate it at the top of getCredentials() (return null or throw). Guard will otherwise happily authenticate a CSRF-less POST.
stateless: true + supportsRememberMe() === true is contradictory. On stateless firewalls return false; otherwise the token listener tries to write a remember-me cookie into a session that doesn't exist.
createAuthenticatedToken() version drift. On Symfony 2.6+ there is a default; on 2.3/2.4 you may need to override it to return the right token class (e.g., UsernamePasswordToken vs PreAuthenticatedToken). If roles behave oddly, check the token class here.
Debugging. Enable the web profiler's Security panel and inspect the token after login; for failure paths, AuthenticationException::getMessageKey() is what you want in your JSON/error output, not getMessage(). Also temporarily log inside getCredentials, getUser, and checkCredentials — the Guard flow is sequential and one null return kills the chain silently.
Extension point to prefer. Rather than subclassing bundle internals, wrap security.encoder_factory and the user provider into small injectable services and keep authenticators thin. When the eventual Symfony upgrade forces you off this package, the only thing you rewrite is the class declaration and a couple of method signatures.
How can I help you explore Laravel packages today?