<?php
namespace App\BackendBundle\Security\Authenticator;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use App\BackendBundle\Helper\SecurityHelper;
class OAuth2Authenticator extends AbstractLoginFormAuthenticator {
private LoggerInterface $logger;
private RequestStack $requestStack;
private RouterInterface $router;
private SessionInterface $session;
private SecurityHelper $securityHelper;
private $sessionOAuthKey = 'oauth2_session_username';
private $sessionEmail = 'oauth2_session_email';
public function __construct(LoggerInterface $logger, RouterInterface $router, RequestStack $requestStack, SecurityHelper $securityHelper) {
$this->logger = $logger;
$this->requestStack = $requestStack;
$this->securityHelper = $securityHelper;
$this->session = $this->requestStack->getSession();
$this->router = $router;
}
public function supports(Request $request): bool {
/*
* Important !!!
* The sessionOAuthKey contains the username after login success and
* the symfony authenticator is called before the AuthorizationRequestResolverListener
* which access the TokenStorage
*/
if ($this->session->has($this->sessionOAuthKey)) {
return true;
}
if (!$request->isMethod('POST')) {
return false;
}
$loginUrl = $this->router->generate('oauth2_login'); // need to create url without parameter
$fullPath = $request->getBaseUrl() . $request->getPathInfo();
if ($loginUrl !== $fullPath) {
return false;
}
return true;
}
protected function getLoginUrl(Request $request): string {
$parameters = $request->query->all();
return $this->router->generate('oauth2_login', $parameters);
}
public function authenticate(Request $request): Passport {
$userIdentifier = $this->session->get($this->sessionOAuthKey);
if (empty($userIdentifier)) {
return $this->passportLoginForm($request);
} else {
return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
}
private function passportLoginForm(Request $request) {
$email = $request->request->get('email', '');
$plaintextPassword = $request->request->get('password', '');
if (empty($email) || empty($plaintextPassword)) {
throw new CustomUserMessageAuthenticationException('Benutzername oder Passwort wurden nicht ausgefüllt.');
}
$request->getSession()->set($this->sessionEmail, $email);
return new Passport(new UserBadge($email), new PasswordCredentials($plaintextPassword));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response {
$request->getSession()->remove(Security::AUTHENTICATION_ERROR);
if ($this->session->has($this->sessionOAuthKey)) {
// on success, let the request continue
return null;
}
/* get username and set in session */
$userName = $token->getUser()->getUsername();
$this->session->set($this->sessionOAuthKey, $userName);
/* redirect back to oauth2 server */
$parameters = $request->query->all();
$url = $this->router->generate('oauth2_authorize', $parameters);
return new RedirectResponse($url);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response {
if ($request->hasSession()) {
$errorText = $this->securityHelper->getLoginErrorText($exception);
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $errorText);
}
$this->session->remove($this->sessionOAuthKey);
$parameters = $request->query->all();
$url = $this->router->generate('oauth2_login', $parameters);
return new RedirectResponse($url);
}
}