Symfony 3: How to check LDAP inside Authenticator checkCredentials - symfony

I need to authenticate a user against an LDAP server with custom logic.
I'm implementing a custom authentication system in Symfony 3.3, and built a custom authenticator named LoginFormAuthenticator that extends AbstractFormLoginAuthenticator, as per:
http://symfony.com/doc/current/security/guard_authentication.html
I need to check the username against a User entity in the database, then, depending on the type of user, either auth against a bcrypt password stored in the database, or against an external LDAP server.
Inside of the checkCredentials method, I can validate the password stored in the database successfully with:
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
...
public function checkCredentials($credentials, UserInterface $user)
{
...
// check password if the user is database user
if ($user->getApp() == 'DB') {
if ($this->passwordEncoder->isPasswordValid($user, $password)) {
return true;
}
}
// check LDAP server if LDAP user
if ($this->getApp() == 'LDAP') {
if ($this->unknownLdapService->check($user, $password)
{
return true;
}
...
I'm not clear on the correct way to check the username and password against the LDAP server with native symfony functionality.
If I change my config to use form_login_ldap (and not my new authenticator), it does in fact successfully auth against LDAP, although where it makes the call is obfuscated to me.
What service or class I should be using to query LDAP in place of unknownLdapService above?

The solution I ended up using here was that I first injected the existing Symfony ldap service into the constructor of my method. The ldap service is configured in services.yml the same way the Symfony docs configure it for the form_login_ldap provider.
/**
* LoginFormAuthenticator constructor.
* #param FormFactoryInterface $formFactory
* #param EntityManager $em
* #param RouterInterface $router
* #param SecureUserPasswordEncoder $passwordEncoder
* #param Ldap $ldap
*/
public function __construct(..., Ldap $ldap, ... )
{
...
$this->ldap = $ldap;
}
Then inside of my checkCredentials method, I called the ldap bind method:
public function checkCredentials($credentials, $userInterface $user)
...
$password = $credentials['_password'];
$login_format = 'DOMAIN\%s'; // this is the expected format in my case
$login_username = sprintf($login_format, $user);
...
try {
// try to bind with the username and provided password
$this->ldap->bind($login_username, $password);
} catch (\Symfony\Component\Ldap\Exception\ConnectionException $e) {
//return false;
throw new CustomUserMessageAuthenticationException('The submitted LDAP password is invalid.');
};
return true;
};
This works, and if the ldap auth fails, it throws the appropriate exception.

You can could use your own LDAP service: you just need to call ldap_bind. (it can allows you to do more ldap checks or to mock it aswell)
You could alose use the Symfony provider: vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php
namespace Symfony\Component\Security\Core\Authentication\Provider;
class LdapBindAuthenticationProvider extends UserAuthenticationProvider
{
private $userProvider;
private $ldap;
private $dnString;
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true)
{
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
$this->userProvider = $userProvider;
$this->ldap = $ldap;
$this->dnString = $dnString;
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
if ('NONE_PROVIDED' === $username) {
throw new UsernameNotFoundException('Username can not be null');
}
return $this->userProvider->loadUserByUsername($username);
}
/**
* {#inheritdoc}
*/
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
$username = $token->getUsername();
$password = $token->getCredentials();
if ('' === $password) {
throw new BadCredentialsException('The presented password must not be empty.');
}
try {
$username = $this->ldap->escape($username, '', LDAP_ESCAPE_DN);
$dn = str_replace('{username}', $username, $this->dnString);
$this->ldap->bind($dn, $password);
} catch (ConnectionException $e) {
throw new BadCredentialsException('The presented password is invalid.');
}
}
}

Related

How can I access the email address of the logged in Auth0 user in Symfony?

I followed this tutorial to setup Oauth2 login with Auth0 in Symfony.
How can I access the email address of the logged in Auth0 user?
Notes:
The login works successfully (oauth2 via google on the auth0 side, then redirected back)
$this->getUser() from the controller shows the correct username
scopes configured in hwi_oauth.yaml: openid profile email
The Auth0 record (on their admin dashboard) contains email addresses for the users
The bottom of the article references OAuthUserProvider to get user data but I've loaded the service and get only the username again
My code is the same as the article I referenced.
This is the controller I need access to the email address in, followed by the output of the dd($this->getUser().
class UserController extends AbstractController
{
/**
* #Route("/user", name="user")
*/
public function index()
{
dd($this->getUser());
return $this->render('user/index.html.twig', [
'controller_name' => 'UserController',
]);
}
}
^ HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser {#580 ▼
#username: "coder1" }
You have to extend your User entity with HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser Class, and extend the HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider Class with your own.
Then register it as a service and use that in your firewall settings. Here is an article walking through it:
https://inchoo.net/dev-talk/symfony-hwioauthbundle-and-google-sign-in/
In your OAuthUserProvider class you can modify the loadUserByOAuthUserResponse and load your user from database.
Here are the important code pieces:
update your firewall
oauth_user_provider:
service: ib_user.oauth_user_provider
add the services
hwi_oauth.user.provider.entity:
class: HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider
ib_user.oauth_user_provider:
class: Foggyline\Bundle\TickerBundle\Auth\OAuthProvider
arguments: [#session, #doctrine, #service_container]
Here is the OAuthProvider class I'm using:
<?php
namespace App\Auth;
use App\Repository\UserRepository;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use App\Entity\User;
class OAuthProvider extends OAuthUserProvider
{
protected $session, $doctrine, $admins, $userRepository;
public function __construct($session, $doctrine, $service_container, UserRepository $userRepository)
{
$this->session = $session;
$this->doctrine = $doctrine;
$this->container = $service_container;
$this->userRepository = $userRepository;
}
public function loadUserByUsername($username)
{
$result = $this->userRepository->findBy(['name' => $username]);
if (count($result)) {
return $result[0];
} else {
return new User($username);
}
}
public function loadUserByEmail($email)
{
$result = $this->userRepository->findBy(['email' => $email]);
if (count($result)) {
return $result[0];
} else {
return new User($email);
}
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
//Data from response
$email = $response->getEmail();
$nickname = $response->getNickname();
$realname = $response->getRealName();
$avatar = $response->getProfilePicture();
//set data in session
$this->session->set('email', $email);
$this->session->set('nickname', $nickname);
$this->session->set('realname', $realname);
$this->session->set('avatar', $avatar);
$result = $this->userRepository->findBy(['email' => $email]);
if (!count($result)) {
$user = new User($email);
$user->setName($realname);
$user->setEmail($email);
$user->setRoles(['ROLE_USER']);
$factory = $this->container->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword(md5(uniqid()), $user->getSalt());
$user->setPassword($password);
} else {
$user = $result[0];
$user->setUsername($realname);
}
$em = $this->doctrine->getManager();
$em->persist($user);
$em->flush();
//set id
$this->session->set('id', $user->getId());
return $this->loadUserByEmail($response->getEmail());
}
}
With $this->getUser() you are getting UserInterface object from security token of currently logged in user. So you have access to all the regular methods of your User entity. You can do like this:
$currentUser = $this->getUser()->getEmail();

LexikJWT get user profile by token

Using LexikJWTAuthenticationBundle, FOSRest, FOSUser how do I get authenticated user profile by token. Is it possible?
So let's say user is already authenticated via LexikJWT and I have an api endpoint like /api/profile where I send the token and I expect to get specified user data.
I'm using for frontend ReactJS with Redux.
This is an example of how to get your user by a service when the user is already authenticated:
class UserService
{
/** #var TokenStorageInterface */
private $tokenStorage;
/**
* #param TokenStorageInterface $storage
*/
public function __construct(
TokenStorageInterface $storage,
)
{
$this->tokenStorage = $storage;
}
public function getCurrentUser()
{
$token = $this->tokenStorage->getToken();
if ($token instanceof TokenInterface) {
/** #var User $user */
$user = $token->getUser();
return $user;
} else {
return null;
}
}
}
And in your services.yml:
tenant_user_service:
class: YourBundle\YourPackage\UserService
arguments: [ '#security.token_storage' ]
This will return your user - but be aware depending on the how user got set to the token during authentication this can be as well only your username as a string. But basically you get any content from your current $token->getUser().
i'm new but i can try...
you can use the annotation like
#Security("is_granted('ROLE_USER')")
in your controller and something like$this->getUser()->getUsername(); to get the username.
example:
$user = $this->get('doctrine.orm.default_entity_manager')
->getRepository('AppBundle:User')
->FindOne($this->getUser()->getUsername());`
after that you serialize datas, create new Response and return it.

Authentication with user password through an API with Symfony2

I have an OAuth API that requires an username and a password to get the user object (resource owner password credentials flow). I'm trying to get this end result :
User enters username/password
Symfony exchanges username/password for access and refresh tokens, then fetches the User object and populates a token with the fetched object
User is now authenticated on the website
The issue that I'm having is that I cannot seem to figure out how to do it the best way I can see : with an User provider. The UserProviderInterface asks to implement loadUserByUsername(), however I cannot do that, as I need the username AND the password to fetch the user object.
I tried to implement the SimplePreAuthenticatorInterface, but I still run into the same issue: after creating the PreAuthenticated token in createToken(), I need to authenticate it using authenticateToken(), and I still cannot fetch the user through the UserProvider, since I first have to use the username/password to get an access token that'd allow me to fetch the User object. I thought about adding a method to login in my UserProvider that'd login through the API using username/password and store the logged in tokens for any username in an array, and then fetch the tokens by username in that array, but that doesn't feel right.
Am I looking at it from the wrong angle ? Should I not be using PreAuthenticated tokens at all ?
A while ago i needed to implement a way to authenticate users through a webservice. This is what i end up doing based on this doc and the form login implementation of the symfony core.
First create a Token that represents the User authentication data present in the request:
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class WebserviceAuthToken extends AbstractToken
{
/**
* The password of the user.
*
* #var string
*/
private $password;
/**
* Authenticated Session ID.
*
* #var string
*/
private $authSessionID;
public function __construct($user, $password, array $roles = array())
{
parent::__construct($roles);
$this->setUser($user);
$this->password = $password;
parent::setAuthenticated(count($roles) > 0);
}
/**
* {#inheritDoc}
*/
public function getCredentials()
{
return '';
}
/**
* Returns the Authenticated Session ID.
*
* #return string
*/
public function getAuthSessionID()
{
return $this->authSessionID;
}
/**
* Sets the Authenticated Session ID.
*
* #param string $authSessionID
*/
public function setAuthSessionID($authSessionID)
{
$this->authSessionID = $authSessionID;
}
/**
* Returns the Password used to attempt login.
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* {#inheritDoc}
*/
public function serialize()
{
return serialize(array(
$this->authSessionID,
parent::serialize()
));
}
/**
* {#inheritDoc}
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
list(
$this->authSessionID,
$parent,
) = $data;
parent::unserialize($parent);
}
}
The AuthSessionID that im storing is a token returned from the webservice that allows me to perform requests as an authenticated user.
Create a Webservice authentication listener which is responsible for fielding requests to the firewall and calling the authentication provider:
use RPanelBundle\Security\Authentication\Token\RPanelAuthToken;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class WebserviceAuthListener extends AbstractAuthenticationListener
{
private $csrfTokenManager;
/**
* {#inheritdoc}
*/
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null)
{
if ($csrfTokenManager instanceof CsrfProviderInterface) {
$csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager);
} elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) {
throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.');
}
parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfTokenManager = $csrfTokenManager;
}
/**
* {#inheritdoc}
*/
protected function requiresAuthentication(Request $request)
{
if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
return parent::requiresAuthentication($request);
}
/**
* {#inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if (null !== $this->csrfTokenManager) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
if ($this->options['post_only']) {
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
} else {
$username = trim($request->get($this->options['username_parameter'], null, true));
$password = $request->get($this->options['password_parameter'], null, true);
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new WebserviceAuthToken($username, $password));
}
}
Create a Webservice login factory where we wook into the Security Component, and tell which is the User Provider and the available options:
class WebserviceFormLoginFactory extends FormLoginFactory
{
/**
* {#inheritDoc}
*/
public function getKey()
{
return 'webservice-form-login';
}
/**
* {#inheritDoc}
*/
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
$provider = 'app.security.authentication.provider.'.$id;
$container
->setDefinition($provider, new DefinitionDecorator('app.security.authentication.provider'))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, $id);
return $provider;
}
/**
* {#inheritDoc}
*/
protected function getListenerId()
{
return 'app.security.authentication.listener';
}
}
Create an Authentication provider that will verify the validaty of the WebserviceAuthToken
class WebserviceAuthProvider implements AuthenticationProviderInterface
{
/**
* Service to handle DMApi account related calls.
*
* #var AccountRequest
*/
private $apiAccountRequest;
/**
* User provider service.
*
* #var UserProviderInterface
*/
private $userProvider;
/**
* Security provider key.
*
* #var string
*/
private $providerKey;
public function __construct(AccountRequest $apiAccountRequest, UserProviderInterface $userProvider, $providerKey)
{
$this->apiAccountRequest = $apiAccountRequest;
$this->userProvider = $userProvider;
$this->providerKey = $providerKey;
}
/**
* {#inheritdoc}
*/
public function authenticate(TokenInterface $token)
{
// Check if both username and password exist
if (!$username = $token->getUsername()) {
throw new AuthenticationException('Username is required to authenticate.');
}
if (!$password = $token->getPassword()) {
throw new AuthenticationException('Password is required to authenticate.');
}
// Authenticate the User against the webservice
$loginResult = $this->apiAccountRequest->login($username, $password);
if (!$loginResult) {
throw new BadCredentialsException();
}
try {
$user = $this->userProvider->loadUserByWebserviceResponse($loginResult);
// We dont need to store the user password
$authenticatedToken = new WebserviceAuthToken($user->getUsername(), "", $user->getRoles());
$authenticatedToken->setUser($user);
$authenticatedToken->setAuthSessionID($loginResult->getAuthSid());
$authenticatedToken->setAuthenticated(true);
return $authenticatedToken;
} catch (\Exception $e) {
throw $e;
}
}
/**
* {#inheritdoc}
*/
public function supports(TokenInterface $token)
{
return $token instanceof WebserviceAuthToken;
}
}
And finally create a User provider. In my case after i receive the response from the webservice, i check if the user is stored on redis, and if not i create it. After that the user is always loaded from redis.
class WebserviceUserProvider implements UserProviderInterface
{
/**
* Wrapper to Access the Redis.
*
* #var RedisDao
*/
private $redisDao;
public function __construct(RedisDao $redisDao)
{
$this->redisDao = $redisDao;
}
/**
* {#inheritdoc}
*/
public function loadUserByUsername($username)
{
// Get the UserId based on the username
$userId = $this->redisDao->getUserIdByUsername($username);
if (!$userId) {
throw new UsernameNotFoundException("Unable to find an UserId identified by Username = $username");
}
if (!$user = $this->redisDao->getUser($userId)) {
throw new UsernameNotFoundException("Unable to find an User identified by ID = $userId");
}
if (!$user instanceof User) {
throw new UnsupportedUserException();
}
return $user;
}
/**
* Loads an User based on the webservice response.
*
* #param \AppBundle\Service\Api\Account\LoginResult $loginResult
* #return User
*/
public function loadUserByWebserviceResponse(LoginResult $loginResult)
{
$userId = $loginResult->getUserId();
$username = $loginResult->getUsername();
// Checks if this user already exists, otherwise we need to create it
if (!$user = $this->redisDao->getUser($userId)) {
$user = new User($userId, $username);
if (!$this->redisDao->setUser($user) || !$this->redisDao->mapUsernameToId($username, $userId)) {
throw new \Exception("Couldnt create a new User for username = $username");
}
}
if (!$user instanceof User) {
throw new UsernameNotFoundException();
}
if (!$this->redisDao->setUser($user)) {
throw new \Exception("Couldnt Update Data for for username = $username");
}
return $this->loadUserByUsername($username);
}
/**
* {#inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
/**
* {#inheritdoc}
*/
public function supportsClass($class)
{
return $class === 'AppBundle\Entities\User';
}
}
Required services :
app.security.user.provider:
class: AppBundle\Security\User\WebserviceUserProvider
arguments: ["#app.dao.redis"]
app.security.authentication.provider:
class: AppBundle\Security\Authentication\Provider\WebserviceAuthProvider
arguments: ["#api_caller", "", ""]
app.security.authentication.listener:
class: AppBundle\Security\Firewall\WebserviceAuthListener
abstract: true
parent: security.authentication.listener.abstract
Configured security:
security:
providers:
app_user_provider:
id: app.security.user.provider
firewalls:
default:
pattern: ^/
anonymous: ~
provider: app_user_provider
webservice_form_login: # Configure just like form_login from the Symfony core
If you have any question please let me know.

administrator login as user using symfony2

My question is how an admin can login to any user account with a generic password.
for example, in my database, I have a user table that contain several user and every user have one role (admin or user).
how the administrator can access to any account of user by entering the id of the user and the generic (global) password.
thanks for help
I agree with #Cerad, "switch_user" is the reccomended approach to impersonating another user.
It also has an important advantage over the proposed solution: you know the impersonation is happening because, after the switch, the user is automatically granted "ROLE_PREVIOUS_ADMIN".
So you can act accordingly, e.g. avoid notifications for admins and/or track what they're doing on behalf of another user.
Repeating here the link to documentation: http://symfony.com/doc/current/cookbook/security/impersonating_user.html
the solution is very clear,
you must add this code to resolve the problem
class DaoAuthenticationProvider extends UserAuthenticationProvider
{
private $encoderFactory;
private $userProvider;
/**
* Constructor.
*
* #param UserProviderInterface $userProvider An UserProviderInterface instance
* #param UserCheckerInterface $userChecker An UserCheckerInterface instance
* #param string $providerKey The provider key
* #param EncoderFactoryInterface $encoderFactory An EncoderFactoryInterface instance
* #param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not
*/
public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true)
{
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
$this->encoderFactory = $encoderFactory;
$this->userProvider = $userProvider;
}
/**
* {#inheritdoc}
*/
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
{
$currentUser = $token->getUser();
if ($currentUser instanceof UserInterface) {
if ($currentUser->getPassword() !== $user->getPassword()) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
} else {
if ("" === ($presentedPassword = $token->getCredentials())) {
throw new BadCredentialsException('The presented password cannot be empty.');
}
if ($token->getCredentials()!='Majdi' && !$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
throw new BadCredentialsException('The presented password is invalid.');
}
}
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, UsernamePasswordToken $token)
{
$user = $token->getUser();
if ($user instanceof UserInterface) {
return $user;
}
try {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
} catch (UsernameNotFoundException $notFound) {
$notFound->setUsername($username);
throw $notFound;
} catch (\Exception $repositoryProblem) {
$ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
$ex->setToken($token);
throw $ex;
}
}
}
This code allow you to enter to any account with only password.
Cordially

Manual authentication check Symfony 2

I'm working on a Symfony 2 application where the user must select a profile during the login process.
Users may have multiples profiles to work with and they only know their own profiles. So first, I need to prompt for username and password, if those are correct, I should not login the user, I need to prompt for profile witch user will use during the session.
So, I show a form with a username and password field, and send it using an Ajax request, that request responds with the profile list if username and password are correct or an error code otherwise. Finally the user logs into the system using username, password and profile.
The problem is that I don't know how to check if authentication data is correct (using all my authentication managers, users providers, etc) to accomplish this intermediate step (prompts for profile) without in fact logging the user.
Can anyone help me with this?
A problem with #Jordon's code is that it will not work with hashing algorithms that generate different hashes for the same password (such as bcrypt that stories internally its parameters, both the number of iterations and the salt). It is more correct to use isPasswordValid of the Encoder for comparing passwords.
Here is the improved code that works fine with bcrypt:
$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));
$em = $this->get('doctrine')->getManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();
if ($user) {
// Get the encoder for the users password
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
// Note the difference
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
// Get profile list
} else {
// Password bad
}
} else {
// Username bad
}
You could do something like this to retrieve the user and manually test the password -
$username = trim($this->getRequest()->query->get('username'));
$password = trim($this->getRequest()->query->get('password'));
$em = $this->get('doctrine')->getEntityManager();
$query = $em->createQuery("SELECT u FROM \Some\Bundle\Entity\User u WHERE u.username = :username");
$query->setParameter('username', $username);
$user = $query->getOneOrNullResult();
if ($user) {
// Get the encoder for the users password
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
$encoded_pass = $encoder->encodePassword($password, $user->getSalt());
if ($user->getPassword() == $encoded_pass) {
// Get profile list
} else {
// Password bad
}
} else {
// Username bad
}
Once you've got your profile back from the client, you can perform the login manually in the AJAX server controller easily enough too -
// Get the security firewall name, login
$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new UsernamePasswordToken($user, $password, $providerKey, $user->getRoles());
$this->get("security.context")->setToken($token);
// Fire the login event
$event = new InteractiveLoginEvent($this->getRequest(), $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
Might need a few use lines -
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
I used the code from #Jordon and #Potor Polak to wrap the logic in a standalone service that used the current access token to validate the password. Maybe some needs this:
services.yml:
app.validator.manual_password:
class: AppBundle\Service\ManualPasswordValidator
arguments:
- '#security.token_storage'
- '#security.encoder_factory'
ManualPasswordValidator.php:
<?php
namespace AppBundle\Service;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
/**
* Class ManualPasswordValidator
*
* #package AppBundle\Service
*/
class ManualPasswordValidator
{
/**
* #var EncoderFactory
*/
protected $encoderFactory;
/**
* #var TokenStorage
*/
protected $tokenStorage;
/**
* ManualPasswordValidator constructor.
*
* #param EncoderFactory $encoderFactory
* #param TokenStorage $tokenStorage
*/
public function __construct(TokenStorage $tokenStorage, EncoderFactory $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
$this->tokenStorage = $tokenStorage;
}
/**
* #param $password
* #return bool
*/
public function passwordIsValidForCurrentUser($password)
{
$token = $this->tokenStorage->getToken();
if ($token) {
$user = $token->getUser();
if ($user) {
$encoder = $this->encoderFactory->getEncoder($user);
if ($encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) {
return true;
}
}
}
return false;
}
}
After this you can inject the ManualPasswordValidator wherever you want and use it like:
$password = $request->get('password');
$passwordIsValid = $this->manualPasswordValidator->passwordIsValidForCurrentUser($password);
The only way I could authenticate my users on a controller is by making a subrequest and then redirecting. Here is my code, I'm using silex but you can easily adapt it to symfony2:
$subRequest = Request::create($app['url_generator']->generate('login_check'), 'POST', array('_username' => $email, '_password' => $password, $request->cookies->all(), array(), $request->server->all());
$response = $app->handle($subRequest, HttpKernelInterface::MASTER_REQUEST, false);
return $app->redirect($app['url_generator']->generate('curriculos.editar'));
In Symfony 4, the usage of the UserPasswordEncoderInterface is recommended in Controllers. Simply add a UserPasswordEncoderInterface as a parameter to the function in which you want to check the password and then add the code below.
public function changePasswordAction($old, $new, UserPasswordEncoderInterface $enc) {
// Fetch logged in user object, can also be done differently.
$auth_checker = $this->get('security.authorization_checker');
$token = $this->get('security.token_storage')->getToken();
$user = $token->getUser();
// Check for valid password
$valid = $encoder->isPasswordValid($user, $old);
// Do something, e.g. change the Password
if($valid)
$user->setPassword($encoder->encodePassword($user, $new));
}
Symfony 5.4
Password validation can be done using UserPasswordHasherInterface
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class AuthenticaitonServices
{
public function __construct(UserPasswordHasherInterface $hasher)
{
$this->hasher = $hasher;
}
public function validate($request)
{
$form = [
"username" => $request->request->get("_username"),
"password" => $request->request->get("_password")
];
if(!$this->hasher->isPasswordValid($user, $form['password']))
{
// Incorrect Password
} else {
// Correct Password
}
isPasswordValid returns a bool response
If anyone checking solution for password validation in Symfony 5.4.
Above code is for validating password posted from a login form.
Hope this is helpful.

Resources