Symfony 3 Custom Form Password Authenticator - php

I'm trying to authenticating via the Custom Form Password Authenticator, I follow the Symfony Example at this page: Custom Form Password Authenticator, I changed a few snippet for put my authentication logic. All seem to work perfectly... But Symfony continued telling me that I'm authenticated as anonymous... Here is the code:
My Custom Athenticator:
class SippyAuthenticator implements SimpleFormAuthenticatorInterface
{
private $sippyAccounts;
public function __construct(SippyAccounts $sippyAccounts)
{
$this->sippyAccounts = $sippyAccounts;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$result = $this->sippyAccounts->login($token->getUsername(), $token->getCredentials());
if($result->isError()) {
throw new CustomUserMessageAuthenticationException('Invalid username or password');
} else {
$roles = array('ROLE_ACCOUNT');
$user = new User($token->getUsername(), $token->getCredentials(), $roles);
$tokenNew = new UsernamePasswordToken( $user, $user->getPassword(), $providerKey, $user->getRoles());
return $tokenNew;
}
}
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);
}
}
This is my security.yml:
security:
providers:
in_memory:
memory: ~
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
simple_form:
authenticator: sippy.authenticator
login_path: login
check_path: login
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/es/overview, roles: ROLE_ACCOUNT }

The problem was that I was generating the User object without call the $userProvider (because I didn't implement it), and symfony later use this object ($userProvider) to retrive the already autenticated user and put it in the session... Below the UserProvider class and the modified SippyAuthenticator:
class SippyUserProvider implements UserProviderInterface
{
private $sippyAccounts;
public function __construct(SippyAccounts $sippyAccounts)
{
$this->sippyAccounts = $sippyAccounts;
}
public function loadUserByUsername($username)
{
$result = $this->sippyAccounts->informationByUsername($username);
if (!$result->isError()) {
$password = null;
return new SippyUser($username, $password, null, array('ROLE_ACCOUNT'));
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof SippyUser) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'AppBundle\Model\SippyUser';
}
}
class SippyAuthenticator implements SimpleFormAuthenticatorInterface
{
private $sippyAccounts;
public function __construct(SippyAccounts $sippyAccounts)
{
$this->sippyAccounts = $sippyAccounts;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$result = $this->sippyAccounts->login($token->getUsername(), $token->getCredentials());
if($result->isError()) {
throw new CustomUserMessageAuthenticationException('Invalid username or password');
} else {
$user = $userProvider->loadUserByUsername($token->getUsername());
$tokenNew = new UsernamePasswordToken( $user, $user->getPassword(), $providerKey, $user->getRoles());
$tokenNew->setAttributes($token->getAttributes());
return $tokenNew;
}
}
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 hope this answer helpful to someone else...

Related

TwoFactorsAuthenticator not connect after second authenticate

here is my problem, I have to put a two-factor authentication. When I fill the first form no worries in the profiler I find my user connected, but when I scan the qrcode of the second form it tells me that the code provided is good but it sends me back to the first page of connection without user in the profiler as if I was disconnected
Could you help me
My Class LOGIN with email & password
class AppCustomAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
public function __construct(private readonly UrlGeneratorInterface $urlGenerator){}
public function authenticate(Request $request): Passport
{
$email = $request->get('login')['email'];
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->get('login')['password']),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
]
);
}
/**
* #throws Exception
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return new RedirectResponse($this->urlGenerator->generate('app_security_setup_fa'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
My Class TwoFactorsAuthenticator
class TwoFactorsAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_security_setup_fa';
public function __construct(private TokenStorageInterface $tokenStorage, private UrlGeneratorInterface $urlGenerator)
{
}
public function supports(Request $request): bool
{
return parent::supports($request)
&& $request->getSession()->has(SecurityController::QR_CODE_KEY);
}
/**
* #throws IncompatibleWithGoogleAuthenticatorException
* #throws SecretKeyTooShortException
* #throws InvalidCharactersException
*/
public function authenticate(Request $request): Passport
{
//Get user from login form
$existingToken = $this->tokenStorage->getToken();
if (null === $existingToken || $existingToken instanceof NullToken) {
throw new UserNotFoundException();
}
$user = $existingToken->getUser();
$qrCode = $request->request->get('qrCode', '');
$secretKey = $request->getSession()->get(SecurityController::QR_CODE_KEY);
$google2fa = new Google2FA();
$google2fa->setSecret($secretKey);
$google2fa->setWindow(1);
if (true !== $google2fa->verifyKey($google2fa->getSecret(), $qrCode)) {
throw new CustomUserMessageAuthenticationException('This code is not valid');
}
$email = $user->getUserIdentifier();
return new SelfValidatingPassport(
new UserBadge($email)
);
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
$currentToken = parent::createToken($passport, $firewallName);
$roles = array_merge($currentToken->getRoleNames(), [DoubleAuthentificationSubscriber::ROLE_2FA_SUCCEED]);
return new PostAuthenticationToken(
$currentToken->getUser(),
$currentToken->getFirewallName(),
$roles
);
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
$this->removeTargetPath($request->getSession(), $firewallName);
}
return new RedirectResponse($this->urlGenerator->generate('app_security_authentification_protected'));
}
}
My Class DoubleAuthentificationSubscriber
class DoubleAuthentificationSubscriber implements EventSubscriberInterface
{
public const ROLE_2FA_SUCCEED = 'ROLE_2FA_SUCCEED';
public const FIREWALL_NAME = 'main';
public function __construct(private RouterInterface $router, private TokenStorageInterface $tokenStorage)
{
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', -10],
];
}
public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
$route = $event->getRequest()->attributes->get('_route');
if (!in_array($route, ['app_security_authentification_protected'], true)) {
return;
}
$currentToken = $this->tokenStorage->getToken();
if (!$currentToken instanceof PostAuthenticationToken) {
$response = new RedirectResponse($this->router->generate('app_login'));
$event->setResponse($response);
return;
}
if (null === $currentToken->getUser() || self::FIREWALL_NAME !== $currentToken->getFirewallName()) {
return;
}
if ($this->hasRole($currentToken, self::ROLE_2FA_SUCCEED)) {
return;
}
$response = new RedirectResponse($this->router->generate('app_security_setup_fa'));
$event->setResponse($response);
}
private function hasRole(TokenInterface $token, string $role): bool
{
return in_array($role, $token->getRoleNames(), true);
}
}
My security.yaml
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
entry_point: App\Security\AppCustomAuthenticator
custom_authenticator:
- App\Security\AppCustomAuthenticator
- App\Security\TwoFactorsAuthenticator
logout:
path: app_logout
# where to redirect after logout
target: app_login
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/logout, roles: PUBLIC_ACCESS }
- { path: ^/setup-2FA, roles: ROLE_USER }
when#test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon
I've tried removing the double authentication and I'm able to log in.
But as soon as I put it back on, it loops on the authentication.

PHPUnit - Symfony authorization with x-api-key

I am using PHPUnit test to write functional test for specific endpoint.
Problem with it is that for the authorization process I have to set X-API-KEY in headers section of the request.
I keep getting an error:
Authentication Required
I am using an valid api key form my test database table and it return this specific error I mentioned above.
public function testDoItSuccessful()
{
$client = static::createClient(
[],
['HTTP_x-api-key' => 'clWD0ggquG1Ok2xOVLIcMmPJtu1uYWG']
);
$client->request(
Request::METHOD_POST,
'/api/v1/do-it',
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json',
],
json_encode($myArray)
);
return $client;
}
As I am new, primarily with authorization process in test env any help is highly appreciated.
Note: I am using Symfony 4.4
I tried THIS.
Error is coming for these two functions in TokenAuthenticator class.
public function supports(Request $request)
{
$hasApiKey = true;
$requestHeaders = $this->getLowerCasedHeaders();
if (!isset($requestHeaders['x-api-key'])) {
$hasApiKey = false;
}
return $hasApiKey;
}
private function getLowerCasedHeaders()
{
$requestHeaders = getallheaders();
return array_change_key_case($requestHeaders, CASE_LOWER);
}
public function getCredentials(Request $request)
{
$requestHeaders = $this->getLowerCasedHeaders();
return $requestHeaders['x-api-key'];
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
if (null === $credentials) {
// Code 401 "Unauthorized"
return null;
}
return $this->entityManager->getRepository(Client::class)->findOneBy(['apiKey' => $credentials]);
}
public function checkCredentials($credentials, UserInterface $user)
{
if ($user->getStatus() != Client::STATUS_ACTIVE) {
throw new AuthenticationException("USER_NOT_ACTIVE", 403);
}
$user->setLastSeen(new DateTime('now'));
$this->entityManager->persist($user);
$this->entityManager->flush();
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
'message' => 'Authentication failed, wrong api key'
];
if ($exception->getCode() == 403) {
$data = ['message' => $exception->getMessage()];
}
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
I have changed apache_request_headers() to getallheaders() and tests are passing with Authentication Required message.
When print_r() the:
$requestHeaders = $this->getLowerCasedHeaders();
I can not see my defined headers from request? It returns Array()..
You need to check how the Client Class parse the headers, specially on the test, there is a part where the code execute a search for the word HTTP_ for the custom header. So basically on your test instead of call only x-api-key you need to add the prefix HTTP_. Try with this:
TokenAuthenticator
public function supports(Request $request)
{
return $request->headers->has('x-api-key');
}
On your test:
public function testDoItSuccessful()
{
crawler = $this->client->request(
Request::METHOD_POST,
'/api/v1/do-it',
[],
[],
['HTTP_x-api-key' => self::TOKEN, 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json']
);
$status = $this->client->getResponse()->getStatusCode();
}
For me what worked is to return a UserInterface with the role defined in your security:
access_control:
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: ROLE_API }
My firewall:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api/
custom_authenticators:
- App\CrediFastCore\Infrastructure\Security\ApiKeyAuthenticator
Since we need ROLE_API
public function authenticate(Request $request): Passport
{
$apiToken = $request->headers->get('X-AUTH-TOKEN');
if (null === $apiToken) {
throw new CustomUserMessageAuthenticationException('No API token provided');
}
if ($this->parameters->get('api_key') != $apiToken) {
//I compare api token with the one I've set on my parameters.yaml
throw new CustomUserMessageAuthenticationException('Wrong API token provided');
}
$user = new User();
$user->setName('api');
$user->setRoles(['ROLE_API']);
return new SelfValidatingPassport(
new UserBadge($apiToken, function () use ($user, $apiToken) {
return $user;
})
);
}
This part was the "fix"
return new SelfValidatingPassport(
new UserBadge($apiToken, function () use ($user, $apiToken) {
return $user;
})
);

AppAuthenticator Symfony

I want to put an authenticator in my API, this anthenticator have to check if the ID given in the headers is in my database. The problem is that I don't understand how this work.
I have read the docs but keep don't understand. (Maybe because of my english).
My API don't need login and password, I just want him to check at every request.
For now I have an error :
Uncaught PHP Exception UnexpectedValueException: "The App\Security\AppAuthenticator::getUser() method must return a UserInterface. You returned boolean
But i'm sure that I have a lot of another problem in my code.
There is my AppAuthenticator :
class AppAuthenticator extends AbstractGuardAuthenticator
{
public function supports(Request $request)
{
return true;
}
public function getCredentials(Request $request)
{
return [
'token' => $request->headers->has('DDMDL-AUTH-TOKEN'),
];
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$apiToken = $credentials['token'];
if (null === $apiToken) {
return;
}
require __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "Entity" . DIRECTORY_SEPARATOR . "sqlconnect.php";
$auth = 'SELECT ID FROM MOBILEDDMDL WHERE ID = :idtel ';
$prepauth = $pdo->prepare($auth);
$prepauth->execute(array(':idtel' => $apiToken));
$user = $prepauth->fetch();
if ($user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Pas autorisé');
}
return $user;
//return $this->em->getRepository(User::class)->findOneBy(['apiToken' => $apiToken]);
}
public function checkCredentials($credentials, UserInterface $user)
{
// todo
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// todo
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
}
public function start(Request $request, AuthenticationException $authException = null)
{
// todo
}
public function supportsRememberMe()
{
// todo
}
}
getUser() want you to return a UserInterface instead instead of your boolean. I believe your User::class extends UserInterface so the commented line was the correct variable to return:
//return $this->em->getRepository(User::class)->findOneBy(['apiToken' => $apiToken]);
My getUser looks like :
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
if(strpos($credentials['identifiant'], '#') !== false){
$user = $this->entityManager->getRepository(Utilisateur::class)->findOneBy(['email' => $credentials['identifiant']]);
} else {
$user = $this->entityManager->getRepository(Utilisateur::class)->findOneBy(['identifiant' => $credentials['identifiant']]);
}
if (!$user) {
throw new CustomUserMessageAuthenticationException('Aucun utilisateur trouvé avec ce nom de compte');
}
return $user;
}
checkCredential is the method which verify the password or whatever and this method will return true of false (if the user connexion is ok or not)
My checkCredential looks like :
public function checkCredentials($credentials, UserInterface $user)
{
$passOk1 = password_verify($credentials['password'], $user->getMdp());
$passOk = md5($credentials['password']) === $user->getMdp();
$credential = false;
if($passOk || $passOk1) {
$user->setDerniereConnexion(new DateTime(date('Y-m-d H:i:s')));
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->entityManager->clear();
$credential = true;
}
return $credential;
}

Symfony 3 bcrypt password does not verify

Most likely I am missing some silly stuff, but I spent quite some time on this, so any help is appreciated.
The authentication is based upon this tutorial
I am using bcrypt to encode the password and it seems that is working properly on user signup.
But when logging in it throws error below, even though the password entered is correct:
Invalid credentials.
I verified that the email and password arrive properly at the login authenticator ( login form is submitted via Ajax ).
Also the getUser() method seems to be doing its job of retrieving $user object and the corresponding password from the db.
The security.yml is set as follows:
security:
encoders:
UsedBundle\Entity\User:
algorithm: bcrypt
This is the registration controller:
namespace UsedBundle\Controller;
use UsedBundle\Form\UserType;
use UsedBundle\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use UsedBundle\Service\sendEmail;
class RegistrationController extends Controller
{
/**
* #Route("/inscription", name="inscription")
*/
public function registerAction(Request $request)
{
$user = new User();
$form = $this->createForm(UserType::class, $user);
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName('user')));
if(!$form->isValid()){
// handle invalid form
}
if ($form->isSubmitted() && $form->isValid()) {
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$user->setUserKey( $user->getEmail() );
$user->setUserKeyTime();
$user->setDateReg();
$em = $this->getDoctrine()->getManager('used');
$em->persist($user);
$em->flush();
return new JsonResponse(array(
'status' => 'ok',
'message' => 'Success!')
);
}
}else{
return $this->render(
'common/register.html.twig',
array('form' => $form->createView())
);
}
}
}
The login form authenticator ( setup as a service ):
namespace UsedBundle\Security;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use UsedBundle\Entity\User;
use UsedBundle\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login_check') {
return;
}
$username = $request->request->get('_email');
$request->getSession()->set(Security::LAST_USERNAME, $username);
$password = $request->request->get('_password');
return array(
'username' => $username,
'password' => $password
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['username'];
$user = $this->container
->get('doctrine')
->getRepository('UsedBundle:User', 'used')
->findOneByemail( $username );
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)){
throw new BadCredentialsException();
}else{
$this->pass_error = 'no error';
}
}
protected function getLoginUrl()
{
return $this->container->get('router')
->generate('homepage');
}
protected function getDefaultSuccessRedirectUrl()
{
return $this->container->get('router')
->generate('homepage');
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// AJAX! Return some JSON
if ($request->isXmlHttpRequest()) {
return new JsonResponse(
array('userId' => $token->getUser()->getId(),
'statut' => 'ok'
)
);
}
// for non-AJAX requests, return the normal redirect
return parent::onAuthenticationSuccess($request, $token, $providerKey);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new JsonResponse(
array('message' => $exception->getMessageKey(),
'statut' => 'error',
'passerror' => $this->pass_error )
);
}
Excerpt of var_dump($user) on checkCredentials() as requested
object(UsedBundle\Entity\User)#329 (15) {
["id":"UsedBundle\Entity\User":private]=>
int(7)
["avatar":"UsedBundle\Entity\User":private]=>
string(11) "dsfdfafadfa"
["name":"UsedBundle\Entity\User":private]=>
string(9) "dfdffadfa"
["password":"UsedBundle\Entity\User":private]=>
string(64) "jjuewij/sc9Af17i+ZXAUcrdiZX83HHMLjTNVSnJ34qGCp6BAxisVtjiG3Nm+uH5"
["plainPassword":"UsedBundle\Entity\User":private]=>
NULL
["email":"UsedBundle\Entity\User":private]=>
string(22) "myemail#gmail.com"
["phone":"UsedBundle\Entity\User":private]=>
string(12) "445454545454"
["roles":"UsedBundle\Entity\User":private]=>
string(9) "ROLE_USER"
["isActive":"UsedBundle\Entity\User":private]=>
bool(true)
as requested, excerpts of var_dump($exception) on onAuthenticationFailure()
object(Symfony\Component\Security\Core\Exception\BadCredentialsException)#322 (8) {
["token":"Symfony\Component\Security\Core\Exception\AuthenticationException":private]=>
object(Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken)#61 (6) {
["credentials":"Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken":private]=>
array(2) {
["username"]=>
string(22) "myemail#gmail.com"
["password"]=>
string(8) "senha444"
}
["guardProviderKey":"Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken":private]=>
string(6) "main_0"
["user":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=>
NULL
["roles":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=>
array(0) {
}
["authenticated":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=>
bool(false)
["attributes":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=>
array(0) {
}
}
["message":protected]=>
string(0) ""
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(78) "/Users/BAMAC/Sites/Symfony1/src/UsedBundle/Security /FormLoginAuthenticator.php"
["line":protected]=>
int(58)
After Edwin put me in the right direction, I managed to get this to work. In reality the encryption was not the only issue.
The changes concern mainly the registration controller, where the code for password encryption was modified.
I also changed the form authenticator which is now based upon this
as the previous base indicated on my question is outdated.
Finally, Symfony are not very much friends with Ajax, so the Ajax URL was adapted to work for dev environment.
Here is the whole code:
security.yml
security:
encoders:
UsedBundle\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: UsedBundle:User
property: email
manager_name: used
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
provider: db_provider
form_login:
login_path: /
username_parameter: _email
check_path: /login_check
guard:
authenticators:
- app.form_login_authenticator
logout:
path: /logout
target: /
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Changes on registration controller:
if ($form->isSubmitted() && $form->isValid()) {
$this->formData = $request->request->get($form->getName('user'));
$this->plainPassword = $this->formData['plainPassword']['first'];
$password = $this->get('security.password_encoder')
->encodePassword($user, $this->plainPassword );
.....
The new authenticator:
namespace UsedBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Security;
class FormLoginAuthenticator extends AbstractGuardAuthenticator
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login_check') {
return;
}
$email = $request->request->get('_email');
$request->getSession()->set(Security::LAST_USERNAME, $email);
$password = $request->request->get('_password');
return array(
'email' => $email,
'password' => $password
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
return $this->user = $this->container
->get('doctrine')
->getRepository('UsedBundle:User', 'used')
->findOneByemail( $email );
}
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)){
throw new BadCredentialsException();
}else{
return true;
}
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$session=$request->getSession();
$session->set('seller_id', $token->getUser()->getId());
$session->set('email', $token->getUser()->getEmail());
return new JsonResponse(
array(
'userId' => $token->getUser()->getId(),
'message' => $this->credentials,
'statut' => 'ok',
'roles' => $token->getUser()->getRoles(),
'email' => $token->getUser()->getEmail(),
)
);
// for non-AJAX requests, return the normal redirect
//return parent::onAuthenticationSuccess($request, $token, $providerKey);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = array(
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
);
return new JsonResponse($data, Response::HTTP_FORBIDDEN);
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = array(
'message' => 'Authentication Required'
);
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
public function supportsRememberMe()
{
return false;
}
}
The login controller:
namespace UsedBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController extends Controller
{
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request)
{
$helper = $this->get('security.authentication_utils');
return $this->render('common/login.html.twig', array(
// last username entered by the user (if any)
'last_username' => $helper->getLastUsername(),
// last authentication error (if any)
'error' => $helper->getLastAuthenticationError(),
));
}
/**
* #Route("/login_check", name="security_login_check")
*/
public function loginCheckAction()
{
// will never be executed
}
}
And this is how the Ajax url should look like while in dev:]
$.ajax({
url: "/app_dev.php/login_check",
type: "POST",
dataType: "json",
data: str,
success: function(data) {
.....

doctrine 2 - The user provider must be an instance of WebserviceUserRepository

i wanted to create an Custom EntityRepository to load a User from the Database, so i followed http://symfony.com/doc/current/cookbook/security/entity_provider.html this tutorial.
Now i get a exception and i do not know why:
{"error":{"code":500,"message":"Internal Server Error","exception":[{"message":"The user provider must be an instance of WebserviceUserRepository (Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider was given)."
If I remove extends EntityRepository from my WebserviceUser repo and also change security.ml and services.yml than the authentication works and the class is an instanceof WebserviceUserRepository.
I am using Symfony 2.7
I hope somebody can help me with that i am starting to pull my hair out ;-)
My Api Key Authenticator looks like that:
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface,
AuthenticationFailureHandlerInterface {
public function createToken(Request $request, $providerKey)
{
// look for an apikey query parameter
$apiKey = $request->query->get('apikey');
// or if you want to use an "apikey" header, then do something like this:
// $apiKey = $request->headers->get('apikey');
if (!$apiKey) {
throw new BadCredentialsException('No API key found');
// or to just skip api key authentication
//return null;
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
**if (!$userProvider instanceof WebserviceUserRepository) {**
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of WebserviceUserRepository (%s was given).',
get_class($userProvider)
)
);
}
$apiKey = $token->getCredentials();
$username = $userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
throw new AuthenticationException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response("Authentication Failed.".$exception->getMessage(), 403);
}
this is my user repo:
class WebserviceUserRepository extends EntityRepository implements UserProviderInterface {
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = 'username';
return $username;
}
public function loadUserByUsername($username)
{
// make a call to your webservice here
$userData = "";
// pretend it returns an array on success, false if there is no user
if (false) {
$password = 'password';
$salt = '';
$roles = array('roles' => 'ROLE_ADMIN');
return new WebserviceUser($username, $password, $salt, $roles);
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(
sprintf(
'Instances of "%s" are not supported.',
$class
)
);
}
return $this->find($user->getUsername());
}
public function supportsClass($class)
{
return $this->getEntityName() === $class
|| is_subclass_of($class, $this->getEntityName());
}
}
i also added the repository class to my entity object:
* #ORM\Table()
* #ORM\Entity(repositoryClass="Moera\RestBundle\Entity\WebserviceUserRepository")
*/
class WebserviceUser implements UserInterface, \Serializable, EquatableInterface
and i also registered bot classes in security.yml and services.yml:
services:
webservice_user_provider:
class: Moera\RestBundle\Entity\WebserviceUserRepository
apikey_authenticator:
class: Moera\RestBundle\Security\ApiKeyAuthenticator
public: false
security:
encoders:
Moera\RestBundle\Entity\WebserviceUser:
algorithm: bcrypt
providers:
webservice_user_provider:
entity:
class: MoeraRestBundle:WebserviceUser
firewalls:
secured_area:
pattern: ^/
stateless: true
simple_preauth:
authenticator: apikey_authenticator
provider: webservice_user_provider
Thank You very much!
Kind Regards,
Johannes

Categories