Symfony 5 - Login with json_login; Login process is not working;

Viewed 3170

first of all I want to let you know, that I'm new to Symfony. I'm on my way to move my PHP project from my own "basic" MVC to Symfony. The project is already running and working well, but I have some issues with adjusting to Symfony.

I started with the basic skeleton, make:user and make:auth. The template worked well. But I failed converting the login process to AJAX and JSON.

I followed this official tutorial: https://symfonycasts.com/screencast/api-platform-security/json-login as well as https://symfony.com/doc/current/security/json_login_setup.html

This is my security.yaml

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

# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
    # used to reload user from session & other features (e.g. switch_user)
    app_user_provider:
        entity:
            class: App\Entity\User
            property: email
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: true
        lazy: true
        provider: app_user_provider

        json_login:
            check_path: app_login
            username_path: email
            password_path: password
            
        guard:
            authenticators:
                - App\Security\UserAuthenticator
        logout:
            path: app_logout
            # where to redirect after logout
            target: home

This is my Controller:

class SecurityController extends AbstractController
{
    // methods={"POST"}

    /**
     * @Route("/api/login", name="app_login")
     */
    public function login(Request $request): Response
    {
        return $this->json([
            'user' => $this->getUser() ? $this->getUser()->getId(): null,
            'error' => 1,
            'content' => $request->getMethod()
            ]);
    }

I removed "methods={"POST"}" because of the Problem with the request method.

First problem

curl -X POST -H "Content-Type: application/json" https://127.0.0.1:8000/api/login -d '{"email": "test@test.de", "password": "1234"}

returns

    <!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url='/api/login'" />

        <title>Redirecting to /api/login</title>
    </head>
    <body>
        Redirecting to <a href="/api/login">/api/login</a>.
    </body>
</html>

content returns always "GET"

'content' => $request->getMethod()

user returns always NULL 'user' => $this->getUser() ? $this->getUser()->getId(): null,

Server Terminal Output:

[PHP        ] [Thu Mar 25 16:05:13 2021] 127.0.0.1:60846 [302]: POST /api/login
[PHP        ] [Thu Mar 25 16:05:13 2021] 127.0.0.1:60846 Closing
[PHP        ] [Thu Mar 25 16:05:13 2021] 127.0.0.1:60848 Accepted
[PHP        ] [Thu Mar 25 16:05:13 2021] [info] Matched route "app_login".
[PHP        ] 
[PHP        ] [Thu Mar 25 16:05:13 2021] [debug] Checking for guard authentication credentials.
[PHP        ] 
[PHP        ] [Thu Mar 25 16:05:13 2021] [debug] Checking support on guard authenticator.
[PHP        ] 
[PHP        ] [Thu Mar 25 16:05:13 2021] [debug] Guard authenticator does not support the request.

Where is my mistake?

EDIT: Added UserAuthenticator.php

<?php

namespace App\Security;

use App\Entity\User;
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 UserAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = 'app_login';

    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 self::LOGIN_ROUTE === $request->attributes->get('_route')
            && $request->isMethod('POST');
    }

    public function getCredentials(Request $request)
    {
        $credentials = [
            'email' => $request->request->get('email'),
            'password' => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        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(User::class)->findOneBy(['email' => $credentials['email']]);

        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        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, string $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->urlGenerator->generate('home'));
    }

    protected function getLoginUrl()
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }
}
1 Answers

In the UserAuthenticator.php I modified the line

public const LOGIN_ROUTE = 'app_login';

to

public const LOGIN_ROUTE = '';

It solved the problem with the Request Method as well as the Login procedure.

Related