I created a login form for my Symfony 5 project.
I want redirect anonymous user to custom page (not the login page) when the user does not have access to my admin panel.
The application controller:
// TestController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
class TestController extends AbstractController
{
/**
* @Route("/admin/panel", name="test")
* @IsGranted("ROLE_ADMIN")
*/
public function index()
{
return $this->render('test/index.html.twig', [
'controller_name' => 'TestController',
]);
}
}
And my configuration settings:
# security.yaml
security:
encoders:
App\Entity\Admin:
algorithm: auto
providers:
admin:
entity:
class: App\Entity\Admin
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin/
guard:
authenticators:
- App\Security\AdminLoginFormAuthenticator
logout:
path: app_logout
main:
anonymous: lazy
My application authenticator:
// AdminLoginFormAuthenticator.php
namespace App\Security;
use App\Entity\Admin;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class AdminLoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return 'app_admin' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('username'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(Admin::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('...!');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('homepage'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_admin');
}
}
Your AdminLoginFormAuthenticator
extends on AbstractFormLoginAuthenticator
, which includes the following:
/**
* Override to control what happens when the user hits a secure page
* but isn't logged in yet.
*
* @return RedirectResponse
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->getLoginUrl();
return new RedirectResponse($url);
}
Reading this, what you need to do is obvious.
Just create your own start()
method to control what happens (e.g. where are they redirected) if they do not have access.
namespace App\Security;
class AdminLoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
// the rest of your class
public function start(Request $request, AuthenticationException $authException = null)
{
$url = 'https:://example.com/whateverurlyouwanttoredirectto';
return new RedirectResponse($url);
}
}