Search code examples
symfonyconfigurationsymfony5symfony-security

Symfony how to configure login in popup with AJAX request


Symfony >= 5.3

  • app isn't API

  • app isn't SPA

  • don't use FOSUserBundle

  • Do I need to set json_login setting in security.yaml?

  • Do I need to set custom_authenticator setting in security.yaml?

  • I tried via form_login and custom_authenticator but I don't need a redirect

  • I tried via json_login(without custom_authenticator) but I am not authenticated


Solution

  • I solved the problem I added class App\Security\ProfileLoginAuthenticator.php

    class ProfileLoginAuthenticator extends Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator
    {
        use Symfony\Component\Security\Http\Util\TargetPathTrait;
    
        public const LOGIN_ROUTE = 'profile_login';
    
        public function __construct(private UrlGeneratorInterface $urlGenerator, private TranslatorInterface $translator)
        {
        }
    
        public function authenticate(Request $request): Passport
        {
            $email = $request->request->get('email', '');
    
            $request->getSession()->set(Security::LAST_USERNAME, $email);
    
            return new Passport(
                new UserBadge($email),
                new PasswordCredentials($request->request->get('password', '')),
                [
                    new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
                    new RememberMeBadge(),
                ]
            );
        }
    
        public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
        {
            if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
                return new JsonResponse([
                    'success' => true,
                    'targetPath' => $targetPath,
                ]);
            }
    
            return new JsonResponse([
                'success' => true,
                'targetPath' => $request->headers->get('referer'),
            ]);
        }
    
        public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
        {
            if ($request->hasSession()) {
                $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
            }
    
            return new JsonResponse([
            'success' => false,
            'message' => $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'),
            ]);
        }
    
        protected function getLoginUrl(Request $request): string
        {
            return $this->urlGenerator->generate(self::LOGIN_ROUTE);
        }
    }
    

    override methods onAuthenticationSuccess, onAuthenticationFailure, return in them JsonResponse

    set this class in security.yaml, in section security->firewalls->custom_authenticator

    security:
        enable_authenticator_manager: true
        password_hashers:
            App\Entity\User:
                algorithm: 'auto'
        providers:
            db_provider:
                entity:
                    class: App\Entity\User
                    property: email
        firewalls:
            ...
            profile:
                pattern: ^/
                provider: db_provider
                form_login:
                    login_path: profile_login
                    check_path: profile_login
                logout:
                    path: profile_logout
                    target: profile_login
                entry_point: form_login
                custom_authenticator: App\Security\ProfileLoginAuthenticator
    

    JS code:

            let $loginForm = $('#login-form');
            $loginForm.on('submit', function (e) {
                e.preventDefault();
                e.stopPropagation();
                $.ajax({
                    url: $loginForm.attr('action'),
                    type: 'POST',
                    data: $loginForm.serialize(),
                    dataType: 'json',
                    success: function (response) {
                        if (response.success) {
                            if (response.hasOwnProperty('targetPath')) {
                                window.location = response.targetPath;
                            }
                            $loginForm.find('.error').html('');
                        } else {
                            if (response.hasOwnProperty('message')) {
                                $loginForm.find('.error').html(response.message);
                            }
                        }
                    }
                })
            });