Search code examples
phpsymfonysymfony-security

Symfony 5 throwing Access Denied Exception


I am trying to use multiple authenticators (for each user role). They were working fine until the last one, which redirects unauthenticated users to URL /dashboard/ to login, but requests to /dashboard are thrown AccessDeniedHttpException:

This is my security.yaml file, which have all the firewalls:

security:
    encoders:
        App\Entity\User:
            algorithm: sha512

    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            pattern: ^/admin/
            anonymous: lazy
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\AdminAuthenticator
            logout:
                path: admin_logout
        expert:
            pattern: ^/dashboard/
            anonymous: lazy
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\ExpertAuthenticator
            logout:
                path: expert_logout
        user:
            anonymous: lazy
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\UserAuthenticator
            logout:
                path: user_logout

    access_control:
        - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin, roles: ROLE_ADMIN }

        - { path: ^/ask, roles: ROLE_USER }

        - { path: ^/dashboard/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/dashboard, roles: ROLE_EXPERT }

I think it is not necessary to paste here the authenticator since it is the Symfony 5 make:auth maker command default which I copied and pasted for all the three authenticators while changing only public const LOGIN_ROUTE = 'user_login'; and onAuthenticationSuccess() function's redirect response path.

I also used the default SecurityController:

namespace App\Controller\Expert;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

/**
 * Class SecurityController
 * @package App\Controller\Expert
 * @Route("/dashboard")
 */
class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="expert_login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        if ($this->getUser()) {
            return $this->redirectToRoute('expert_index');
        }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();
        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('expert/security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
    }

    /**
     * @Route("/logout", name="expert_logout")
     */
    public function logout()
    {
        $this->addFlash('success', 'Session closed');
        return $this->redirectToRoute('expert_index');
    }
}

Whenever I access to /admin, /admin/whatever or /ask, /ask/whatever I am redirected to its respective login form (/admin/login and /ask/dashboard).

I can access directly to /dashboard/login if I am not logged from another authenticator or if I am logged with ROLE_EXPERT. If not, I am getting the mentioned exception.

Well, do you see what is going on?


Solution

  • You should move - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY } as last access_control entry.

    That's because Symfony parses it as you're writing it (think about it like it's a FIFO queue) and, as / could be accessed in anonymous way, associated token will be anonymous (it won't try to read from session or whatever).