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);
}
}