Custom authentication in a Symfony 3 using external REST API - symfony

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 :
to implement password validation in the getUser method of the authenticator, and have checkCredentials method always return true.
to disable the refreshUser method of UserProvider

Related

How i can implement refresh token with custom jwt token generation with LexikJWTAuthenticationBundle?

Currently i create in api platform jwt token with custom symfony controller, provider and encode with JWTEncoderInterface, use authentification come from external api. I have users but not password in my database. How implement refresh token with that system?
security.yml
providers:
# used to reload user from session & other features (e.g. switch_user)
user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/_(profiler|wdt)
security: false
api:
pattern: ^/api/
stateless: true
anonymous: true
provider: user_provider
guard:
authenticators:
- app.authenticator
main:
anonymous: true
stateless: true
pattern: /authentication_token
My authenticator
<?php
namespace App\Security\Guard;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Throwable;
class Authenticator extends AbstractGuardAuthenticator
{
private JWTEncoderInterface $jwtEncoder;
public function __construct(JWTEncoderInterface $jwtEncoder)
{
$this->jwtEncoder = $jwtEncoder;
}
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request) : bool
{
return $request->headers->has('Authorization Bearer');
}
/**
* Called on every request. Return whatever credentials you want to
* be passed to getUser() as $credentials.
*/
public function getCredentials(Request $request) : string
{
return $request->headers->get('Authorization Bearer');
}
public function getUser($token, UserProviderInterface $userProvider) : UserInterface
{
try {
$user = $this->jwtEncoder->decode($token);
} catch(Throwable $e) {
throw new AuthenticationException($e->getMessage());
}
return $userProvider->loadUserByUsername($user['email']);
}
public function checkCredentials($credentials, UserInterface $user) : bool
{
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) : JsonResponse
{
$failure = [
'message' => $exception->getMessage()
];
return new JsonResponse($failure, Response::HTTP_UNAUTHORIZED);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null) : JsonResponse
{
$data = [
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe() : bool
{
return false;
}
}
Controller
#[Route('/authentication_token', name: 'security_authentication_token', methods: ['POST'])]
public function createAuthenticationToken(
Request $request,
CreateTokenAuthenticationHandler $createTokenAuthenticationHandler,
ValidatorInterface $validator
): JsonResponse
{
$createTokenAuthentication = CreateTokenAuthentication::createFromPayload($request->request->all());
$errors = $validator->validate($createTokenAuthentication);
if (count($errors) > 0) {
foreach ($errors as $error) {
$message[]= sprintf("Field %s: %s ", $error->getPropertyPath(), $error->getMessage());
}
return new JsonResponse($message, JsonResponse::HTTP_BAD_REQUEST);
}
$token = $createTokenAuthenticationHandler->handle($createTokenAuthentication);
return new JsonResponse(['token' => $token], JsonResponse::HTTP_OK);
}
My handler where i generate token
public function handle(CreateTokenAuthentication $createTokenAuthentication) : string
{
$user = $this->processAuthentication($createTokenAuthentication);
return $this->jwtEncoder->encode(
[
'email' => $user->getEmail(),
'role' => $user->getRoles()
]
);
}
Thanks.
I use manual custom refresh token with symfony route in database with token and expiration date.

Symfony 4 - Custom GuardAuthenticator doesn't set remember me cookie

I'm building a custom GuardAuthenticator to login with a token on a specific route. According to the documentation if supportsRememberMe() returns true and remember_me is activated in the firewall, the remember me cookie should be set, but it's not (although it is set if I use a form login authentication on another route).
The route:
/**
* #Route("/login/token/{id}/{token}/{force}", defaults={"force"=0}, name="login_token")
*/
public function loginToken()
{
}
The GuardAuthenticator:
<?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\HttpKernel\Exception\HttpException;
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\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class TokenLoginAuthenticator extends AbstractGuardAuthenticator
{
use TargetPathTrait;
private $em;
private $force;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function supports(Request $request)
{
return 'login_token' === $request->attributes->get('_route') && $request->isMethod('GET');
}
public function getCredentials(Request $request)
{
$credentials = [
'id' => $request->attributes->get('id'),
'token' => $request->attributes->get('token')
];
$this->force = $request->attributes->get('force');
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$user = $this->em->getRepository(User::class)->find($credentials['id']);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('No user found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
if ($user->getToken() === $credentials['token']) {
return true;
}
throw new HttpException(403, "Forbidden");
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
throw new HttpException(403, "Forbidden");
}
public function start(Request $request, AuthenticationException $authException = null)
{
}
public function supportsRememberMe()
{
return true;
}
}
The security config:
security:
encoders:
App\Entity\User:
id: 'App\Security\PasswordEncoder'
providers:
in_memory: { memory: ~ }
orm:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
form_login:
login_path: login
check_path: login
provider: orm
csrf_token_generator: security.csrf.token_manager
default_target_path: homepage
logout:
path: /logout
target: /
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
# by default, the feature is enablered by checking a
# checkbox in the login form (see below), uncomment the
# following line to always enable it.
# always_remember_me: true
guard:
provider: orm
authenticators:
- App\Security\TokenLoginAuthenticator
In Symfony 5.2.6 dont forget to add new RememberMeBadge() to the authenticate function:
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
public function authenticate(Request $request): PassportInterface
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
new RememberMeBadge(),
]
);
}
}
Remember me cookie will be set if all of the following are met:
The supportsRememberMe() method returns true.
The remember_me key in the firewall is configured.
The (default) _remember_me parameter is sent in the request. This is usually done by having a _remember_me checkbox in a login form (but it can be sent as url param (?_remember_me=1), or we can configure the firewall remember_me key to always_remember_me.
The onAuthenticationSuccess() method returns a Response object.

symfony3 guard login form doesn't authenticate [duplicate]

This question already has an answer here:
Symfony & Guard: "The security token was removed due to an AccountStatusException"
(1 answer)
Closed 5 years ago.
I try to make a form login authentication with guard (symfony 3.2) but it doesn't work.
The authentication is working, but when I'm redirected to the home page (accueil), I'm redirected to the login page without anthentication.
If I put in the controler of my home page
$user = $this->get('security.token_storage')->getToken();
dump($user); die;
I can see my user, the role but he is not authenticated.
DashboardController.php on line 23:
PostAuthenticationGuardToken {#133 ▼
-providerKey: "main"
-user: User {#457 ▶}
-roles: array:1 [▼
0 => Role {#120 ▼
-role: "ROLE_SUPERADMIN"
}
]
-authenticated: false
-attributes: []
}
What I've missed ?
Security.ym
security:
encoders:
EntBundle\Entity\User\User:
algorithm: bcrypt
providers:
database:
entity:
class: EntBundle:User\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: ~
logout: ~
guard:
authenticators:
- ent.login_authenticator
TestAuthenticator.php
namespace EntBundle\Security;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class TestAuthenticator extends AbstractGuardAuthenticator
{
private $em;
private $router;
public function __construct(EntityManager $em, RouterInterface $router)
{
$this->em = $em;
$this->router = $router;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
return;
}
return [
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
];
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['username'];
return $this->em->getRepository('EntBundle:User\User')->findOneBy(['username' => $username]);
}
public function checkCredentials($credentials, UserInterface $user)
{
// this is just for test
return true;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$url = $this->router->generate('accueil');
return new RedirectResponse($url);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
public function supportsRememberMe()
{
return false;
}
}
DashboardController.php
namespace EntBundle\Controller\Dashboard;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DashboardController extends Controller
{
/**
* #Route("/accueil", name="accueil")
*/
public function indexAction()
{
$user = $this->get('security.token_storage')->getToken();
dump($user); die;
return $this->render('EntBundle:dashboard:dashboard_structure.html.twig');
}
/**
* #Route("/login", name="login")
*/
public function loginAction()
{
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('EntBundle::login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
/**
* #Route("/logout", name="logout")
*/
public function logoutAction()
{
}
}
EDIT:
Thanks leo_ap for your help but the problem doesnt come from there.
The config session is like this :
session:
handler_id: session.handler.native_file
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
and if I check in the save path folder I have session file created but not authenticated.
_sf2_attributes|a:1:{s:26:"_security.main.target_path";s:29:"http://localhost:8000/accueil";}_sf2_flashes|a:0:{}_sf2_meta|a:3:{s:1:"u";i:1488245179;s:1:"c";i:1488244922;s:1:"l";s:1:"0";}
If I try the normal login_form with security.yml it's working fine...
I've try with handler_id and save_path at null with no success.
EDIT2:
I've found why I'm always redirected to the login page, because I'm logged out!
[2017-02-28 09:16:34] security.INFO: The security token was removed due to an AccountStatusException. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException(code: 0): at /home/philippe/Documents/symfony/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php:86)"}
and in GuardAuthenticationProvider.php (86)
The listener *only* passes PreAuthenticationGuardToken instances.
This means that an authenticated token (e.g.PostAuthenticationGuardToken)
is being passed here, which happens if that token becomes "not authenticated" (e.g. happens if the user changes between requests).
In this case, the user should be logged out, so we will return an AnonymousToken to accomplish that.
But Why ???
May be your Session that isn't persisting the token. Check your Session configuration, inside: config.yml. in the framework option, there is session. See how the handler_id and save_path are configured. It may be that your php instalation is unable to handle the sessions on the configured path. Try to put null to handler_id and save_path to force php use its own build in configurations to handle sessions.
config.yml file:
framework:
{ .. Other configurations ..}
session:
handler_id: null
save_path: null
{ .. More configurations ..}

Symfony3 Guard and login form

I try to use Guard to make a login form instead of the security.yml way.
Getuser and checkcredential are ok.
onAuthenticationSuccess is ok (if I put a dump($token); die; in onAuthenticationSuccess I can see my user in the token) and redirect to /accueil.
But when it arrived on /accueil it's send back to /login because user authentication is always on anon !
Impossible to find a solution :c/
Firewall in security.yml :
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login_firewall:
pattern: ^/login$
anonymous: true
main:
pattern: ^/
anonymous: ~
logout: ~
switch_user: true
guard:
provider: database
authenticators:
- ent.login_authenticator
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
securityController
/**
* #Route("/login", name="login")
*
*/
public function loginAction(Request $request)
{
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return $this->redirectToRoute('accueil');
}
$authenticationUtils = $this->get('security.authentication_utils');
$exception = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('EntBundle::login.html.twig', [
'last_username' => $lastUsername,
'error' => $exception,
]);
}
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
loginAuthenticator:
public function __construct(RouterInterface $router, UserPasswordEncoder $passwordEncoder, EntityManager $em) {
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
$this->em = $em;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login_check' ) {
return null;
}
$request->getSession()->set(Security::LAST_USERNAME, $request->request->get('_username'));
return array(
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
try {
return $this->em->getRepository('EntBundle:User\User')->findOneBy(array('username' => $credentials ));
}
catch (UsernameNotFoundException $e) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
}
public function checkCredentials($credentials, UserInterface $user) {
$plainPassword = $credentials['password'];
if ($this->passwordEncoder->isPasswordValid($user, $plainPassword)) {
return true;
}
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// dump($token); die;
$url = $this->router->generate('accueil');
return new RedirectResponse($url);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
sorry for the late response, the piece of code would look something like this to set the token in a symfony3 application:
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
and the actual setting of the token part will be like:
$token = new UsernamePasswordToken($user, $user->getPassword(), "firewall goes here for example: main", $user->getRoles());
$this->get("security.token_storage")->setToken($token);
i hope i helped you with this :)

Symfony2. Simple preauth not working after deploying on hosting, because it try to authenticate one more time after success authentication

In my Symfony application I have two firewalls:
main:
pattern: ^/(?!login).+
stateless: true
simple_preauth:
authenticator: app_bundle.api_key_authenticator
provider: api_key_user_provider
anonymous: ~
logout: ~
login:
pattern: ^/login$
stateless: false
simple_preauth:
authenticator: app_bundle.email_password_authenticator
provider: email_user_provider
anonymous: ~
logout: ~
After authenticating through /login User getting apiKey, which allow get access to another part of app.
On my local server all works fine. But after deploying on hosting authentication not working.
After debugging I see, that email authenticator try to authenticate twice.
So first time it returns correct Token, but second time it returns NULL, because loadUserByUsername() method get username instead of email (authentication is possible only through email).
This is my email authenticator:
<?php
namespace AppBundle\Security\Authentication;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class EmailPasswordAuthenticator implements SimplePreAuthenticatorInterface
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function createToken(Request $request, $providerKey)
{
if ($request->get('email') && $request->get('password')) {
return new UsernamePasswordToken(
$request->get('email'),
$request->get('password'),
$providerKey
);
}
throw new AuthenticationException('Authentication failed');
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
try {
$user = $userProvider->loadUserByUsername($token->getUsername());
} catch (UsernameNotFoundException $e) {
throw new AuthenticationException('Invalid username or password');
}
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
if ($passwordValid) {
return new UsernamePasswordToken(
$user,
$user->getPassword(),
$providerKey,
$user->getRoles()
);
}
throw new AuthenticationException('Password incorrect');
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof UsernamePasswordToken
&& $token->getProviderKey() === $providerKey;
}
}
This is my User provider:
<?php
namespace AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use AppBundle\Repository\UserRepository;
use Symfony\Component\Security\Core\User\UserInterface;
class EmailUserProvider implements UserProviderInterface
{
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function loadUserByUsername($email)
{
$user = $this->userRepository->findOneBy(array('email' => $email));
if (null === $user) {
throw new UsernameNotFoundException(
sprintf(
'User with email "%s" not found',
$email
)
);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getEmail());
}
public function supportsClass($class)
{
return $class === 'AppBundle\Entity\User';
}
}
Please, help me to understand, what's going on. What the reason of this weird behavior it can be?
Thanks a lot for any help!
UPD. As I can see, authenticate() method calls first time by Symfony\Component\Security\Http\Firewall\SimplePreAuthenticationListener, second time - by Symfony\Component\Security\Http\Firewall\AccessListener.
Problem in this part of code:
// AccessListener
if (!$token->isAuthenticated()) {
$token = $this->authManager->authenticate($token);
$this->tokenStorage->setToken($token);
}
Solved. Problem was in my groups and roles tables, them was empty and without data from them User cannot be authenticated.

Resources