Switch to new authentication system

This commit is contained in:
Jannis Portmann 2021-09-14 14:03:27 +02:00
parent d277d9c93e
commit f2eed9d848
3 changed files with 38 additions and 76 deletions

View file

@ -22,9 +22,8 @@ security:
main: main:
lazy: true lazy: true
provider: app_user_provider provider: app_user_provider
guard: custom_authenticators:
authenticators: - App\Security\AppAuthenticator
- App\Security\AppAuthenticator
logout: logout:
path: app_logout path: app_logout
# where to redirect after logout # where to redirect after logout

View file

@ -7,6 +7,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
/** /**
@ -14,7 +15,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
* @ORM\Table(name="`user`") * @ORM\Table(name="`user`")
* @UniqueEntity(fields={"email"}, message="There is already an account with this email") * @UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/ */
class User implements UserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
/** /**
* @ORM\Id * @ORM\Id
@ -92,6 +93,14 @@ class User implements UserInterface
* *
* @see UserInterface * @see UserInterface
*/ */
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* @deprecated since Symfony 5.3, use getUserIdentifier instead
*/
public function getUsername(): string public function getUsername(): string
{ {
return (string) $this->username; return (string) $this->username;
@ -117,11 +126,11 @@ class User implements UserInterface
} }
/** /**
* @see UserInterface * @see PasswordAuthenticatedUserInterface
*/ */
public function getPassword(): string public function getPassword(): string
{ {
return (string) $this->password; return $this->password;
} }
public function setPassword(string $password): self public function setPassword(string $password): self

View file

@ -2,105 +2,59 @@
namespace App\Security; namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
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\Security;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait; use Symfony\Component\Security\Http\Util\TargetPathTrait;
class AppAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface class AppAuthenticator extends AbstractLoginFormAuthenticator
{ {
use TargetPathTrait; use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login'; public const LOGIN_ROUTE = 'app_login';
private $entityManager; private UrlGeneratorInterface $urlGenerator;
private $urlGenerator;
private $csrfTokenManager;
private $passwordHasher;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordHasherInterface $passwordHasher) public function __construct(UrlGeneratorInterface $urlGenerator)
{ {
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordHasher = $passwordHasher;
} }
public function supports(Request $request) public function authenticate(Request $request): PassportInterface
{ {
return self::LOGIN_ROUTE === $request->attributes->get('_route') $email = $request->request->get('email', '');
&& $request->isMethod('POST');
}
public function getCredentials(Request $request) $request->getSession()->set(Security::LAST_USERNAME, $email);
{
$credentials = [ return new Passport(
'email' => $request->request->get('email'), new UserBadge($email),
'password' => $request->request->get('password'), new PasswordCredentials($request->request->get('password', '')),
'csrf_token' => $request->request->get('_csrf_token'), [
]; new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
$request->getSession()->set( ]
Security::LAST_USERNAME,
$credentials['email']
); );
return $credentials;
} }
public function getUser($credentials, UserProviderInterface $userProvider) public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{ {
$token = new CsrfToken('authenticate', $credentials['csrf_token']); if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
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->passwordHasher->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($targetPath);
} }
return new RedirectResponse($this->urlGenerator->generate('user_page')); return new RedirectResponse($this->urlGenerator->generate('user_page'));
} }
protected function getLoginUrl() protected function getLoginUrl(Request $request): string
{ {
return $this->urlGenerator->generate(self::LOGIN_ROUTE); return $this->urlGenerator->generate(self::LOGIN_ROUTE);
} }
} }