Search code examples
phpsymfonysymfony4symfony-security

Symfony security configuration, check_path goes to defined controller


I'm new into symfony, and I'm trying to create my own authentication. (I've external auth system so I've declared my User class and UserProvider) I've configured some routes, controllers and security yml, but when I send login form I've end up on error that says

Full authentication is required to access this resource.

Here is my config for security:

security:
encoders:
    App\Domain\User\ValueObject\User: bcrypt
providers:
    UserProvider:
        id: App\Providers\UserProvider

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    login:
        pattern: ^/login
        anonymous: ~
    login_others:
        pattern: ^/login/.*$
        anonymous: ~
    register:
        pattern: ^/register.*$
        anonymous: ~
    bye:
        pattern: ^/bye
        anonymous: ~
    main:
        provider: UserProvider
        pattern: ^/.*
        form_login:
            # submit the login form here
            check_path: user.login.check

            # the user is redirected here when they need to log in
            login_path: /login
        logout:
            path:   /logout
            target: /login
            invalidate_session: false

# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
     - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
     - { path: ^/login/.*$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
     - { path: ^/register.*$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
     - { path: ^/bye$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
     - { path: ^/.+$, roles: ROLE_USER }

And my controller for routes looks like this:

    class LoginController extends Controller
{
    /**
     * @param AuthenticationUtils $authUtils
     * @param TokenStorageInterface $tokenStorage
     * @return Response
     *
     * @Route("/login", name="user.login", methods="GET")
     */
    public function loginAction(
        AuthenticationUtils $authUtils,
        TokenStorageInterface $tokenStorage
    ) {
        if (!is_null($tokenStorage->getToken()) && in_array('ROLE_USER', $tokenStorage->getToken()->getRoles())) {
            return $this->redirect($this->generateUrl('dashboard'));
        }

        $error = $authUtils->getLastAuthenticationError();
        $lastUsername = $authUtils->getLastUsername();

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

    /**
     *
     * @Route("/login_check", name="user.login.check", methods={"POST"})
     */
    public function loginCheckAction()
    {

    }

Any idea what I'm doing wrong? I'm almost sure it's configuration problem, but it appears that I don't understand it.


Solution

  • I've eventually figured out a bit more working configuration

    security:
        encoders:
            App\Domain\User\ValueObject\User: bcrypt
        providers:
            UserProvider:
                id: App\Security\UserProvider
    
        firewalls:
            dev:
                pattern: ^/(_(profiler|wdt)|css|images|js)/
                security: false
            main:
                anonymous: ~
                provider: UserProvider
                pattern: ^/.*
                form_login:
                    login_path: user.login
                logout:
                    path:   /logout
                    target: user.login
                    invalidate_session: false
    
        # Easy way to control access for large sections of your site
        # Note: Only the *first* access control that matches will be used
        access_control:
             - { path: ^/login.*$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
             - { path: ^/register.*$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
             - { path: ^/.+$, roles: ROLE_USER }
    

    As you see I don't have configured check path for my form_login. That is because It didn't want to replace anonymous user token for the logged one, so I eventually created my own endpoint for login check and did it manually that looks like this:

    /**
     * @param Request $request
     * @param PasswordAuthenticator $authenticator
     * @param UserProvider $provider
     * @param Session $session
     * @param TokenStorageInterface $storage
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
     *
     * @Route("/login/check", name="user.login.check", methods={"POST"})
     */
    public function checkLoginUser(
        Request $request,
        PasswordAuthenticator $authenticator,
        UserProvider $provider,
        Session $session,
        TokenStorageInterface $storage
    ) {
        $token = $authenticator->createToken(
            $request,
            $request->request->get('_username'),
            $request->request->get('_password'),
            'UserProvider'
        );
    
        $authenticator->supportsToken($token, 'UserProvider');
        try {
            $newToken = $authenticator->authenticateToken($token, $provider, $token->getUser());
    
            $storage->setToken($newToken);
            $session->set('_security_main', serialize($newToken));
    
            return $this->redirect($this->generateUrl('dashboard'));
        } catch (CustomUserMessageAuthenticationException $e) {
            $error = $e->getMessage();
        }
    
        return $this->render('user/login.twig', [
            'error'         => $error,
            'last_username' => $request->request->get('_username'),
        ]);
    
    }