I would like to write a basic login form, which authenticates users by sending a request to an external REST API. The external API receives the login/password and return 200 (ok) if the credentials are correct. However, I can't implement it via the UserProviderInterface, because the external REST API give me the password in the reply. (I can't fill the user password in the loadUserByUsername method).
I found a valid solution here, but it uses classes that have been removed in Symfony 3 : Symfony2 custom connection by web service
I made a test with a custom Authenticator, which only checks that the password is "toto", but I get a redirection loop and my dummy UserProvider is still called :
<?php
namespace AppBundle\Security\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimpleFormAuthenticatorInterface;
class WebserviceAuthenticator implements SimpleFormAuthenticatorInterface
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$user = new WebserviceUser($token->getUsername(), $token->getCredentials(), null, ['ROLE_ADMIN']);
// HERE : call the external REST API
if ($token->getCredentials() === 'toto') {
$token = new UsernamePasswordToken(
$user,
$user->getPassword(),
'main',
$user->getRoles()
);
return $token;
}
throw new CustomUserMessageAuthenticationException('Invalid username or password');
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof UsernamePasswordToken
&& $token->getProviderKey() === $providerKey;
}
public function createToken(Request $request, $username, $password, $providerKey)
{
return new UsernamePasswordToken($username, $password, $providerKey);
}
}
I got it working with that implementation :
security.yml
providers:
webservice:
id: AppBundle\Security\User\WebserviceUserProvider
encoders:
AppBundle\Entity\WebserviceUser: plaintext
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
provider: webservice
pattern: ^/
form_login:
check_path: login
login_path: login
use_forward: true
logout: ~
guard:
authenticators:
- app.webservice_authenticator
login:
pattern: ^/login$
anonymous: ~
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/cache, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
services.yml
services:
app.webservice_authenticator:
class: AppBundle\Security\User\WebserviceAuthenticator
User Provider
namespace AppBundle\Security\User;
use AppBundle\Entity\WebserviceUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class WebserviceUserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
return new WebserviceUser($username, null, null, ['ROLE_USER']);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $user;
}
public function supportsClass($class)
{
return WebserviceUser::class === $class;
}
}
Authenticator
<?php
namespace AppBundle\Security\User;
use AppBundle\Service\RestClient;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
class WebserviceAuthenticator extends AbstractFormLoginAuthenticator
{
private $container;
private $restClient;
public function __construct(ContainerInterface $container, RestClient $restClient)
{
$this->container = $container;
$this->restClient = $restClient;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || $request->getMethod() != 'POST') {
return;
}
$username = $request->request->get('_username');
$request->getSession()->set(Security::LAST_USERNAME, $username);
$password = $request->request->get('_password');
return array(
'username' => $username,
'password' => $password
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
//dump($credentials); die();
if (array_key_exists('username', $credentials) == false) {
return null;
}
$username = $credentials['username'];
$password = strtoupper($credentials['password']);
if ($username == '') {
return null;
}
// Here the business code, provide your own implementtion
if ($this->restClient->IsValidLogin($username, $password)) {
return new WebserviceUser($username, $password, null, ['ROLE_USER']);
} else {
throw new CustomUserMessageAuthenticationException('Invalid credentials');
}
}
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// AJAX! Return some JSON
if ($request->isXmlHttpRequest()) {
return new JsonResponse(array('message' => $exception->getMessageKey()), 403);
}
// for non-AJAX requests, return the normal redirect
return parent::onAuthenticationFailure($request, $exception);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// AJAX! Return some JSON
if ($request->isXmlHttpRequest()) {
return new JsonResponse(array('userId' => $token->getUser()->getId()));
}
// for non-AJAX requests, return the normal redirect
return parent::onAuthenticationSuccess($request, $token, $providerKey);
}
protected function getLoginUrl()
{
return $this->container->get('router')
->generate('login');
}
protected function getDefaultSuccessRedirectUrl()
{
return $this->container->get('router')
->generate('homepage');
}
}
The trick seems to be :
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With