Symfony authentication doesn't work - symfony

I am writing regarding the Symfony authentication problem, which occurred last month and I still cannot find a solution, so I am dependent on you :D
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Table(name="app_users")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
//id,username,password
public function getSalt()
{
return null;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials()
{
}
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized);
}
}
This is my User entity and now below you can see my security.yaml which I think I configured right:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|img|js)/
security: false
main:
anonymous: true
http_basic: ~
provider: db_provider
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Whenever I am trying to access /admin route it shows me http-basic login but whenever I input "admin, admin" nothing happens. IN my database I have one user with username:admin and password admin which is hashed by bcrypt.
Not using authentication then everything works as it should, I get all data from the database as it should be after authentication.
Thanks for your help guys!

Your problem
As Med already pointed out, your User entity has the ROLE_USER role as default:
/* App/Entity/User.php */
public function getRoles()
{
return array('ROLE_USER');
}
Your access_control configuration on the other hand states that the route /admin can only be accessed with a user that has the ROLE_ADMIN role:
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
That means, your user "admin" lacks the sufficient role to access /admin.
Solution
You need to be able to assign multiple roles to the user. One possible way is saving the roles as a concatenated string and returning it as an array:
/* App/Entity/User.php */
/**
* #ORM\Column(name="roles", type="string")
* #var string
*/
private $roles;
/**
* Get the user roles as an array of strings
* #return array
*/
public function getRoles()
{
return explode($roles, ',');
}
You can even add some methods to manage your roles via the entity class:
/* App/Entity/User.php */
/**
* Add a new role
* #param string $role name of the role
* #return this
*/
public function addRole($role)
{
$roles = $this->getRoles();
if (array_search($role, $roles) === false) {
$roles[] = $role;
$this->roles = implode(',', $roles);
}
return $this;
}
/**
* Remove a role
* #param string $role name of the role
* #return this
*/
public function removeRole($role)
{
$roles = $this->getRoles();
$searchResult = array_search($role, $roles);
if ($searchResult !== false) {
unset($roles[$searchResult]);
$this->roles = implode(',', $roles);
}
return $this;
}

Related

Symfony 5: ldap authentication with custom user entity

I want to implement the following authentication scenario in symfony 5:
User sends a login form with username and password, authentication is processed against an LDAP server
if authentication against the LDAP server is successful :
if there is an instance of my App\Entity\User that as the same username as the ldap matching entry, refresh some of its attributes from the ldap server and return this entity
if there is no instance create a new instance of my App\Entity\User and return it
I have implemented a guard authenticator which authenticates well against the LDAP server but it's returning me an instance of Symfony\Component\Ldap\Security\LdapUser and I don't know how to use this object to make relation with others entities!
For instance, let's say I have a Car entity with an owner property that must be a reference to an user.
How can I manage that ?
Here is the code of my security.yaml file:
security:
encoders:
App\Entity\User:
algorithm: auto
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
my_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: "%env(LDAP_BASE_DN)%"
search_dn: "%env(LDAP_SEARCH_DN)%"
search_password: "%env(LDAP_SEARCH_PASSWORD)%"
default_roles: ROLE_USER
uid_key: uid
extra_fields: ['mail']
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: my_ldap
guard:
authenticators:
- App\Security\LdapFormAuthenticator
I finally found a good working solution.
The missing piece was a custom user provider.
This user provider has the responsibility to authenticate user against ldap and to return the matching App\Entity\User entity. This is done in getUserEntityCheckedFromLdap method of LdapUserProvider class.
If there is no instance of App\Entity\User saved in the database, the custom user provider will instantiate one and persist it. This is the first user connection use case.
Full code is available in this public github repository.
You will find below, the detailed steps I follow to make the ldap connection work.
So, let's declare the custom user provider in security.yaml.
security.yaml:
providers:
ldap_user_provider:
id: App\Security\LdapUserProvider
Now, configure it as a service, to pass some ldap usefull string arguments in services.yaml.
Note since we are going to autowire the Symfony\Component\Ldap\Ldap service, let's add this service configuration too:
services.yaml:
#see https://symfony.com/doc/current/security/ldap.html
Symfony\Component\Ldap\Ldap:
arguments: ['#Symfony\Component\Ldap\Adapter\ExtLdap\Adapter']
Symfony\Component\Ldap\Adapter\ExtLdap\Adapter:
arguments:
- host: ldap
port: 389
# encryption: tls
options:
protocol_version: 3
referrals: false
App\Security\LdapUserProvider:
arguments:
$ldapBaseDn: '%env(LDAP_BASE_DN)%'
$ldapSearchDn: '%env(LDAP_SEARCH_DN)%'
$ldapSearchPassword: '%env(LDAP_SEARCH_PASSWORD)%'
$ldapSearchDnString: '%env(LDAP_SEARCH_DN_STRING)%'
Note the arguments of the App\Security\LdapUserProvider come from env vars.
.env:
LDAP_URL=ldap://ldap:389
LDAP_BASE_DN=dc=mycorp,dc=com
LDAP_SEARCH_DN=cn=admin,dc=mycorp,dc=com
LDAP_SEARCH_PASSWORD=s3cr3tpassw0rd
LDAP_SEARCH_DN_STRING='uid=%s,ou=People,dc=mycorp,dc=com'
Implement the custom user provider :
App\Security\LdapUserProvider:
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\LdapInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class LdapUserProvider implements UserProviderInterface
{
/**
* #var Ldap
*/
private $ldap;
/**
* #var EntityManager
*/
private $entityManager;
/**
* #var string
*/
private $ldapSearchDn;
/**
* #var string
*/
private $ldapSearchPassword;
/**
* #var string
*/
private $ldapBaseDn;
/**
* #var string
*/
private $ldapSearchDnString;
public function __construct(EntityManagerInterface $entityManager, Ldap $ldap, string $ldapSearchDn, string $ldapSearchPassword, string $ldapBaseDn, string $ldapSearchDnString)
{
$this->ldap = $ldap;
$this->entityManager = $entityManager;
$this->ldapSearchDn = $ldapSearchDn;
$this->ldapSearchPassword = $ldapSearchPassword;
$this->ldapBaseDn = $ldapBaseDn;
$this->ldapSearchDnString = $ldapSearchDnString;
}
/**
* #param string $username
* #return UserInterface|void
* #see getUserEntityCheckedFromLdap(string $username, string $password)
*/
public function loadUserByUsername($username)
{
// must be present because UserProviders must implement UserProviderInterface
}
/**
* search user against ldap and returns the matching App\Entity\User. The $user entity will be created if not exists.
* #param string $username
* #param string $password
* #return User|object|null
*/
public function getUserEntityCheckedFromLdap(string $username, string $password)
{
$this->ldap->bind(sprintf($this->ldapSearchDnString, $username), $password);
$username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER);
$search = $this->ldap->query($this->ldapBaseDn, 'uid=' . $username);
$entries = $search->execute();
$count = count($entries);
if (!$count) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
if ($count > 1) {
throw new UsernameNotFoundException('More than one user found');
}
$ldapEntry = $entries[0];
$userRepository = $this->entityManager->getRepository('App\Entity\User');
if (!$user = $userRepository->findOneBy(['userName' => $username])) {
$user = new User();
$user->setUserName($username);
$user->setEmail($ldapEntry->getAttribute('mail')[0]);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
return $user;
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this
* method is not called.
*
* #return UserInterface
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
}
return $user;
// Return a User object after making sure its data is "fresh".
// Or throw a UsernameNotFoundException if the user no longer exists.
throw new \Exception('TODO: fill in refreshUser() inside ' . __FILE__);
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass($class)
{
return User::class === $class || is_subclass_of($class, User::class);
}
}
Configure the firewall to use our custom user provider:
security.yaml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: ldap_user_provider
logout:
path: app_logout
guard:
authenticators:
- App\Security\LdapFormAuthenticator
Write an authentication guard:
App\SecurityLdapFormAuthenticator:
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LdapFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $urlGenerator;
private $csrfTokenManager;
public function __construct(UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager)
{
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
}
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $userProvider->getUserEntityCheckedFromLdap($credentials['username'], $credentials['password']);
if (!$user) {
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
//in this scenario, this method is by-passed since user authentication need to be managed before in getUser method.
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$request->getSession()->getFlashBag()->add('info', 'connected!');
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_homepage'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
My user entity looks like this:
`App\Entity\User`:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password = 'password is not managed in entity but in ldap';
/**
* #ORM\Column(type="string", length=255)
*/
private $userName;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function setUserName(string $userName): self
{
$this->userName = $userName;
return $this;
}
}
For Symfony 6 I do like this.
No extra implementation
security:
role_hierarchy:
ROLE_USER: ROLE_USER
ROLE_ADMIN: [ROLE_USER, ROLE_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
pmh_db:
entity:
class: App\Entity\User
property: username
pmh_ldap:
ldap:
service: Symfony\Component\Ldap\Ldap
base_dn: '%base_dn%'
search_dn: '%search_dn%'
search_password: '%search_password%'
default_roles: 'ROLE_USER'
uid_key: '%uid_key%'
extra_fields: ['email']
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
pattern: ^/
provider: pmh_db
switch_user: { role: ROLE_ALLOWED_TO_SWITCH }
login_throttling:
max_attempts: 5
form_login_ldap:
login_path: app_login
check_path: app_login
service: Symfony\Component\Ldap\Ldap
dn_string: 'DOMAIN\{username}'
query_string: null
default_target_path: /
logout:
path: /logout
target: /
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
# by default, the feature is enabled by checking a
# checkbox in the login form (see below), uncomment the
# following line to always enable it.
always_remember_me: 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: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
- { path: '^/', roles: ROLE_USER }

Multiple user classes

I have 4 different user types in a system (on top of Symfony 2). Each type have some specific fields and behaviour, but all of them have a common base. So it seems it would be good idea to implement single class for each user extending the same superclass.
How can it be achieved? All I have found on the topic is some RollerworksMultiUserBundle.
Using table inheritance in the ORM level and OOP inheritance. Go for Single Table Inheritance if performance is critical (no JOINs) or Class Table Inheritance if you are a purist.
E.g.
Common base class:
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* #ORM\Entity(repositoryClass="Some\Bundle\Repository\UserRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="userType", type="string")
* #ORM\DiscriminatorMap({
* "userType1" = "UserType1",
* "userType2" = "UserType2",
* "userType3" = "UserType3",
* "userType4" = "UserType4"
* })
*/
abstract class User implements AdvancedUserInterface
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=250, unique=true)
*/
protected $email;
/**
* #ORM\Column(type="string", length=128, nullable=true)
*/
protected $password;
// other fields
public function getSalt()
{
return "some salt number";
}
public function getUsername()
{
return $this->email;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials() {}
public function isCredentialsNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isAccountNonExpired()
{
return true;
}
public function isEnabled()
{
return true;
}
public function equals(UserInterface $user)
{
return $user->getUsername() === $this->getUsername() || $user->getEmail() === $this->getEmail();
}
}
The children classes are straightforward (below an example for class UserType1 only):
/**
* #ORM\Entity
*/
class UserType1 extends User
{
// fields of UserType1 class go here
public function getRoles()
{
return array('ROLE_USER_TYPE_1', 'ROLE_USER');
}
}
The rest is pretty much like in the examples. In security.yml:
security:
encoders:
Some\Bundle\Repository\User:
algorithm: sha512
encode_as_base64: false
iterations: 1000
providers:
provider1:
entity: { class: "SomeBundle:User" }
role_hierarchy:
ROLE_USER_TYPE_1: ROLE_USER
ROLE_USER_TYPE_2: ROLE_USER
ROLE_USER_TYPE_3: ROLE_USER
ROLE_USER_TYPE_4: ROLE_USER
firewalls:
firewall1:
pattern: ^/
provider: provider1
form_login:
login_path: /login
check_path: /auth
post_only: true
username_parameter: email
password_parameter: password
always_use_default_target_path: true
default_target_path: /
logout:
path: /logout
target: /login
anonymous: ~
The repository class:
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\User\UserInterface;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$qb = $this->createQueryBuilder('u');
$query = $qb->where('LOWER(u.email) = :email')
->setParameter('email', strtolower($username))
->getQuery();
try {
$user = $query->getSingleResult();
}
catch (NoResultException $e) {
throw new UsernameNotFoundException('User not found.', null, $e);
}
return $user;
}
public function supportsClass($class)
{
return $this->getEntityName() === $class ||
is_subclass_of($class, $this->getEntityName());
}
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->getId());
}
}

Combining fos_oauth authenticator with single key authenticator

I set up two authentication methods for my api:
Private token authenticator by following this tutorial
FOSOAuthServerBundle system for access_token auth: https://github.com/FriendsOfSymfony/FOSOAuthServerBundle
Both works like a charm separately.
I tried to combine those systems with this security config:
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ALLOWED_TO_SWITCH: ~
ROLE_SUPPORT: ~
ROLE_ADMIN: [ROLE_SONATA_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_SUPPORT, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
api_key_user:
id: security.user.provider.api_key
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
oauth_token:
pattern: ^/oauth/v2/token
security: false
api:
pattern: ^/api
stateless: true
simple_preauth:
authenticator: security.authentication.authenticator.api_key
fos_oauth: true
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
anonymous: ~
logout:
path: /logout
switch_user: true
If I try to get an access token with this curl command:
curl "http://localhost:8000/oauth/v2/token?client_id=1_2rqa1al0trwgso8g8co4swsks48cwsckgc8cokswkcgos4csog&client_secret=25a78plm6c2ss044k4skckkwoo8kw4kcoccg8sg0skook4sgwg&grant_type=password&username=test&password=test
It works an I get an access_token, but when I try to use it:
curl -X GET http://localhost:8000/api/changelogs.json -H "Authorization: Bearer MmI2OWNkNjhjMGYwOTUyNDA2OTdlMDBjNjA1YmI3MjVhNTBiMTNhMjI0MGE1YmM3NzgwNjVmZWZmYWNhM2E4YQ" | json_pp
I get:
{
"error" : "invalid_grant",
"error_description" : "The provided access token is invalid."
}
By deactivating my single_preauth api key authenticator, it's works and I can access to my API.
It seems my api key authenticator block all another system.
Here, my ApiKeyAuthenticator class:
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
private $userProvider;
/**
* #param ApiKeyUserProvider $userProvider
*/
public function __construct(ApiKeyUserProvider $userProvider)
{
$this->userProvider = $userProvider;
}
/**
* {#inheritdoc}
*/
public function createToken(Request $request, $providerKey)
{
$apiKey = str_replace('Bearer ', '', $request->headers->get('Authorization', ''));
if (!$apiKey) {
throw new BadCredentialsException('No API key given.');
}
return new PreAuthenticatedToken('anon.', $apiKey, $providerKey);
}
/**
* {#inheritdoc}
*/
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
$apiKey = $token->getCredentials();
$username = $this->userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
throw new AuthenticationException('The provided access token is invalid.');
}
$user = $this->userProvider->loadUserByUsername($username);
return new PreAuthenticatedToken(
$user,
$apiKey,
$providerKey,
$user->getRoles()
);
}
/**
* {#inheritdoc}
*/
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new JsonResponse([
'error' => 'invalid_grant',
'error_description' => $exception->getMessage()
], 401);
}
}
But I can't find why.
How to combine this two authenticator methods?
Thanks for help.
Finaly found how to handle it but not sure it's the better and proper way.
Don't hesitate to suggest improvements on comments! ;)
First of all, remove fos_oauth key from security.yml file. It should looks like:
security:
firewalls:
# [...]
api:
pattern: ^/api
stateless: true
# This will handle both oauth access token and simple private token
simple_preauth:
authenticator: security.authentication.authenticator.api_key
# [...]
On ApiKeyUserProvider::getUsernameForApiKey method, you will search on both custom api key manager and OAuth access token manager.
The complete class should look like this.
class ApiKeyUserProvider implements UserProviderInterface
{
/**
* #var UserManagerInterface
*/
private $userManager;
/**
* #var ApiKeyManager
*/
private $apiKeyManager;
/**
* #var AccessTokenManagerInterface
*/
private $accessTokenManager;
/**
* #param UserManagerInterface $userManager
* #param ApiKeyManager $apiKeyManager
* #param AccessTokenManagerInterface $accessTokenManager
*/
public function __construct(UserManagerInterface $userManager, ApiKeyManager $apiKeyManager, AccessTokenManagerInterface $accessTokenManager)
{
$this->userManager = $userManager;
$this->apiKeyManager = $apiKeyManager;
$this->accessTokenManager = $accessTokenManager;
}
/**
* #param string $apiKey
*
* #return string|null
*/
public function getUsernameForApiKey($apiKey)
{
// FOSOAuth system
$token = $this->accessTokenManager->findTokenByToken($apiKey);
if ($token) {
return $token->getUser()->getUsername();
}
// Private key system
return $this->apiKeyManager->getUsernameForToken($apiKey);
}
/**
* {#inheritdoc}
*/
public function loadUserByUsername($username)
{
return $this->userManager->findUserByUsername($username);
}
/**
* {#inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
throw new UnsupportedUserException();
}
/**
* {#inheritdoc}
*/
public function supportsClass($class)
{
return 'FOS\UserBundle\Model\User' === $class;
}
}
And voila! Both Private and OAuth token are correctly managed.

Bad credentials with Fosuser symfony2

I'm trying since 3 hours to install and configure FOSuser, which many developpers adviced me to use it.I wanted actually to make a normal login form without to use FOS but I had a lot of problems.I followed all steps in the documentation. the installation was ok , the configuration also but everytime when I try to log in , it shows "Bad credentials".So i find somehow this command that I executed :php app/console fos:user:create i give name-email-password. it work somehow but only with what i write, I mean when I register user in my registration form and try to log in it shows "Bad credentials".I hope that I was clear else please tell me what do you need to know
Here are my Users.php where i have all my users info to login...
namespace test\indexBundle\Document;
use FOS\UserBundle\Model\User as BaseUser;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
*
* #MongoDB\Document
*/
class Users extends BaseUser
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\String
*/
protected $userId;
/**
* #MongoDB\String
*/
protected $userEmail;
/**
* #MongoDB\String
*/
protected $userPassword;
/**
* #MongoDB\String
*/
protected $salt;
/**
* #MongoDB\Int
*/
protected $isActive;
public function __construct()
{
parent::__construct();
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
/**
* Set id
*
* #param id $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Get id
*
* #return id $id
*/
public function getId()
{
return $this->id;
}
/**
* Set userId
*
* #param string $userId
*/
public function setUserId()
{
$this->userId = $this->salt;
}
/**
* Get userId
*
* #return string $userId
*/
public function getUserId()
{
return $this->userId;
}
/**
* Set userName
*
* #param string $userName
*/
public function setUserName($userName)
{
$this->userName = $userName;
}
/**
* Get userName
*
* #return string $userName
*/
public function getUserName()
{
return $this->username;
}
/**
* Set userEmail
*
* #param string $userEmail
*/
public function setUserEmail($userEmail)
{
$this->userEmail = $userEmail;
}
/**
* Get userEmail
*
* #return string $userEmail
*/
public function getUserEmail()
{
return $this->userEmail;
}
/**
* Set userPassword
*
* #param string $userPassword
*/
public function setPassword($userPassword)
{
$this->userPassword = $userPassword;
}
/**
* Get userPassword
*
* #return string $userPassword
*/
public function getPassword()
{
return $this->userPassword;
}
/**
* #inheritDoc
*/
public function getSalt()
{
return '';
}
/**
* #inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id
) = unserialize($serialized);
}
}
and here my security.yml:
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
test\indexBundle\Document\Users:
algorithm: sha1
encode_as_base64: false
iterations: 1
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: /login_check
login_path: /login
provider: fos_userbundle
post_only: true
use_forward: false
username_parameter: email
password_parameter: password
failure_path: null
failure_forward: false
target_path_parameter: redirect_url
logout:
path: /logout
target: /blog
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
and login function:
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
if ($this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY'))
{
return $this->redirect($this->generateUrl('index_homepage'));
}
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR))
{
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
}
else
{
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('indexBundle:index:logIn.html.twig', array(
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}
I might be wrong but I think FOSUserBundle requires a user to be activated after it's been created if you use the form registration, it's send out and email with a link I believe. I think you can use app/console fos:user:activate to activate if there is no email.

Custom validation in symfony2

I am new to symfony. I have decided to move my wheel with Symfony version 2.
In my user form:
I would like to validate uniqueness of email in the database .
I would like to also validate password with confirm password field .
I could find any help in the symfony2 doc.
This stuff took me a while to track down too, so here's what I came up with. To be honest I'm not really sure about the getRoles() method of the User entity, but this is just a test setup for me. Context items like that are provided solely for clarity.
Here are some helpful links for further reading:
A validation constraint that forces uniqueness
A field type for repeated (double-entry-verified) fields
I set this all up to make sure it worked as a UserProvider for security as well since I figured you were probably doing that. I also assumed you were using the email as the username, but you don't have to. You could create a separate username field and use that. See Security for more information.
The Entity (only the important parts; autogenerateable getters/setters are omitted):
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints as DoctrineAssert;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*
* list any fields here that must be unique
* #DoctrineAssert\UniqueEntity(
* fields = { "email" }
* )
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length="255", unique="true")
*/
protected $email;
/**
* #ORM\Column(type="string", length="128")
*/
protected $password;
/**
* #ORM\Column(type="string", length="5")
*/
protected $salt;
/**
* Create a new User object
*/
public function __construct() {
$this->initSalt();
}
/**
* Generate a new salt - can't be done as prepersist because we need it before then
*/
public function initSalt() {
$this->salt = substr(str_shuffle(str_repeat('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',5)),0,5);
}
/**
* Is the provided user the same as "this"?
*
* #return bool
*/
public function equals(UserInterface $user) {
if($user->email !== $this->email) {
return false;
}
return true;
}
/**
* Remove sensitive information from the user object
*/
public function eraseCredentials() {
$this->password = "";
$this->salt = "";
}
/**
* Get the list of roles for the user
*
* #return string array
*/
public function getRoles() {
return array("ROLE_USER");
}
/**
* Get the user's password
*
* #return string
*/
public function getPassword() {
return $this->password;
}
/**
* Get the user's username
*
* We MUST have this to fulfill the requirements of UserInterface
*
* #return string
*/
public function getUsername() {
return $this->email;
}
/**
* Get the user's "email"
*
* #return string
*/
public function getEmail() {
return $this->email;
}
/**
* Get the user's salt
*
* #return string
*/
public function getSalt() {
return $this->salt;
}
/**
* Convert this user to a string representation
*
* #return string
*/
public function __toString() {
return $this->email;
}
}
?>
The Form class:
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class UserType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options) {
$builder->add('email');
/* this field type lets you show two fields that represent just
one field in the model and they both must match */
$builder->add('password', 'repeated', array (
'type' => 'password',
'first_name' => "Password",
'second_name' => "Re-enter Password",
'invalid_message' => "The passwords don't match!"
));
}
public function getName() {
return 'user';
}
public function getDefaultOptions(array $options) {
return array(
'data_class' => 'Acme\UserBundle\Entity\User',
);
}
}
?>
The Controller:
namespace Acme\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Acme\UserBundle\Entity\User;
use Acme\UserBundle\Form\Type\UserType;
class userController extends Controller
{
public function newAction(Request $request) {
$user = new User();
$form = $this->createForm(new UserType(), $user);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
// encode the password
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user->getPassword(), $user->getSalt());
$user->setPassword($password);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('AcmeUserBundle_submitNewSuccess'));
}
}
return $this->render('AcmeUserBundle:User:new.html.twig', array (
'form' => $form->createView()
));
}
public function submitNewSuccessAction() {
return $this->render("AcmeUserBundle:User:submitNewSuccess.html.twig");
}
Relevant section of security.yml:
security:
encoders:
Acme\UserBundle\Entity\User:
algorithm: sha512
iterations: 1
encode_as_base64: true
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
main:
entity: { class: Acme\UserBundle\Entity\User, property: email }
firewalls:
secured_area:
pattern: ^/
form_login:
check_path: /login_check
login_path: /login
logout:
path: /logout
target: /demo/
anonymous: ~
Check out http://github.com/friendsofsymfony there is a UserBundle that have that functionality. You can also check http://blog.bearwoods.com where there is a blog post about adding a custom field, constraint and validator for Recaptcha.
Thoose resources should get you started on the right path if you are still running into trouble people are generally helpful and friendly on irc at #symfony-dev on the Freenode network. On Freenoce there is also a general channel #symfony where you can ask questions about how to use stuff where #symfony-dev is for the development of Symfony2 Core.
Hopefully this will help you move forward with your project.
I think the main thing you need to look out for when creating your custom validator is the constant specified in the getTargets() method.
If you change
self::PROPERTY_CONSTRAINT
to:
self::CLASS_CONSTRAINT
You should be able to access all properties of the entity, not only a single property.
Note: If you are using annotations to define your constraints you will now need to move the annotation which defines your validator up to the top of the class as it is now applicable to the whole Entity and not just the single property.
You should be able to get everything you need from the docs. Specifically the constraints which has information on email checking. There's also documentation on writing custom validators.
I've done all like on http://symfony.com/doc/2.0/book/validation.html
My config:
validator.debit_card:
class: My\Validator\Constraints\DebitCardValidator
tags:
- { name: validator.constraint_validator, alias: debit_card }
tried to use it with
#assert:DebitCard
#assert:debitCard
#assert:debit_card
but it is not triggered?
unique email from database
validation.yml
Dashboard\ArticleBundle\Entity\Article:
constraints:
#- Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: senderEmail
- Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: { fields: senderEmail, message: This email already exist }
Password with confirm password
$builder->add('password', 'repeated', array(
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password',
'required' => false,
));

Resources