I am having a problem in generating jwt token in symfony using this package
lexik/LexikJWTAuthenticationBundle . I followed carefully the documentation and have an invalid credentials error ( credentials are correct )
this is 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'
App\Entity\User:
algorithm: 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:
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login_check # or api_login_check as defined in config/routes.yaml
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
# username_path: username
# password_path: password
api:
pattern: ^/api
methods: [POST]
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
and this is my user entity :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
#[ApiResource(formats: ["json"])]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
private $email;
#[ORM\Column(type: 'json')]
private $roles = [];
#[ORM\Column(type: 'string')]
private $password;
private $plainPassword;
/**
* #return mixed
*/
public function getPlainPassword()
{
return $this->plainPassword;
}
/**
* #param mixed $plainPassword
*/
public function setPlainPassword($plainPassword): void
{
$this->plainPassword = $plainPassword;
}
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 getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #deprecated since Symfony 5.3, use getUserIdentifier instead
*/
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 PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* #see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
$this->plainPassword = null;
}
}
now when i make the request from post man to get the token it tells me (the route is correct and well defined ) :
{
"code": 401,
"message": "Invalid credentials."
}
Your problem is the configuration. Just add them back and replace username_path: username with username_path: email.
json_login:
check_path: /api/login_check
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
Also add in the config/packages/lexik_jwt_authentication.yaml:
user_identity_field: email
Related
Versions I Used:
using (symfony 5.2.6) with (api-platform 2.7.0) alongside (php 8.0.3) and (postgres 13)
Description
used maker bundle to generate a User Entity and configured the security.yaml to encode passwords already tried auto and bcrypt or even argon2i non of them seems to work and hash the passwords
Possible Solution
as the symfony documentation describes it should automatically encode passwords if we are using security bundle and implementing UserInterface but this one seems like a bug cause i have tried many things nothing works . maybe using UserPasswordEncoderInterface but this one should not be used when User class is implementing UserInterface anyway i hope someone check this and tell me i'm wrong otherwise seems like a bug.
security.yaml:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# 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: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# 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: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
# or require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*
# - { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
# the 'path' value can be any valid regular expression
# (this one will match URLs like /api/post/7298 and /api/comment/528491)
# - { path: ^/api/(post|comment)/\d+$, roles: ROLE_USER }
User.php:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use App\Entity\Trait\HasDateTime;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ApiResource
* #ORM\Entity(repositoryClass=UserRepository::class)
* #ORM\Table(name="`user`")
* #ORM\HasLifecycleCallbacks
*/
class User implements UserInterface
{
use HasDateTime;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
#[Assert\NotBlank]
#[Assert\Email]
private $email;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
#[Assert\NotBlank]
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
#[Assert\NotBlank]
private $password;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): string
{
return (string) $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->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* #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;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* #see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
tried to save data in the database each time it saved passwords as plaintext.
also tried to remove the trait class thought it might conflict some how which seems strange(i know) (but i just gave it a try) it was not hashing passwords even after removing the trait. what am i doing wrong or missing here?
thanks in advance
here is the solution:
i had to create a class that implements DataPersister then in there we have to encode the password . here is the code:
<?php
namespace App\DataPersister;
use App\Entity\User;
use ApiPlatform\Core\DataPersister;
use Doctrine\ORM\EntityManagerInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserDataPersister implements DataPersisterInterface
{
public function __construct(private EntityManagerInterface $entityManager, private UserPasswordEncoderInterface $userPasswordEncoderInterface)
{
}
/**
* Is the data supported by the persister?
*/
public function supports($data): bool
{
return $data instanceof User;
}
/**
* #param User $data
* #return object|void Void will not be supported in API Platform 3, an object should always be returned
*/
public function persist($data)
{
if ($data->getPassword()) {
$data->setPassword(
$this->userPasswordEncoderInterface->encodePassword($data, $data->getPassword())
);
}
$this->entityManager->persist($data);
$this->entityManager->flush();
}
/**
* Removes the data.
*/
public function remove($data)
{
$this->entityManager->remove($data);
$this->entityManager->flush();
}
}
these are my first steps with Symfony. I try to implement a simple user management with Easyadmin Bundle in Symfony 4.4. I followed the Tutorial on symfony.com and most of it is working correctly (Sign up form, backend login, backend security, backend listing of users from database).
My Problem is the creation and updating of a user in the Easyadmin backend. When I try to create a new user, I see the correct fields, I do enter some data and if I click "Save changes" it throws the following error:
An exception occurred while executing 'INSERT INTO app_users (username, email, roles, password, is_active) VALUES (?, ?, ?, ?, ?)' with params ["testname", "test#example.com", "a:1:{i:0;s:9:\"ROLE_USER\";}", null, 1]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'password' cannot be null
Column 'password' cannot be null is pretty clear: I need to provide it with some encoded password string.
I think the data in the plain password field I enter is not encoded and/or not processed by the setPassword() method in my User entity.
As far as I understand some SO answers and the Symfony documentation it should work automagically!? I don't know. I tried to create an AdminController that extends EasyAdminController and hook it in somewhere in the persisting of the user entity, but I couldn't get it to work. (Something like this: https://stackoverflow.com/a/54749433)
How do I process/encode the plainpassword that it is saved to the password field in database?
User entity:
// /src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* #ORM\Table(name="app_users")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity("username")
* #UniqueEntity("email")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=254, unique=true)
* #Assert\NotBlank(groups={"edit"})
*/
private $username;
/**
* #ORM\Column(type="string", length=254, unique=true)
* #Assert\NotBlank()
* #Assert\Email(groups={"edit"})
*/
private $email;
/**
* #ORM\Column(type="array")
*/
private $roles;
/**
* #Assert\Length(max=4096)
*/
private $plainPassword;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct(){
$this->roles = array('ROLE_USER');
$this->isActive = true;
}
public function getId(){
return $this->id;
}
public function getUsername(){
return $this->username;
}
public function setUsername($username){
$this->username = $username;
}
public function getEmail(){
return $this->email;
}
public function setEmail($email){
$this->email = $email;
}
public function getIsActive(){
return $this->isActive;
}
public function setIsActive($is_active){
$this->isActive = $is_active;
}
public function getRoles(){
return $this->roles;
}
public function setRoles($roles){
$roles[] = 'ROLE_USER';
$this->roles = $roles;
}
public function getPlainPassword(){
return $this->plainPassword;
}
public function setPlainPassword($password){
$this->plainPassword = $password;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password){
$this->password = $password;
}
public function getSalt(){
return null;
}
public function eraseCredentials(){}
public function isAccountNonExpired(){
return true;
}
public function isAccountNonLocked(){
return true;
}
public function isCredentialsNonExpired(){
return true;
}
public function isEnabled(){
return $this->isActive;
}
/** #see \Serializable::serialize() */
public function serialize(){
return serialize(array(
$this->id,
$this->username,
$this->email,
$this->password,
$this->isActive,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized){
list (
$this->id,
$this->username,
$this->email,
$this->password,
$this->isActive,
) = unserialize($serialized, array('allowed_classes' => false));
}
}
Security.yaml:
# /config/packages/security.yaml
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
users_in_memory: { memory: null }
our_db_provider:
entity:
class: App\Entity\User
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
pattern: ^/
provider: our_db_provider
form_login:
login_path: login
check_path: login
default_target_path: account
always_use_default_target_path: true
csrf_token_generator: security.csrf.token_manager
logout:
path: /logout
target: /login
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/account, roles: ROLE_USER }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Easyadmin.yaml:
# /config/packages/easy_admin.yaml
easy_admin:
design:
menu:
- { entity: User, label: 'Benutzerverwaltung', icon: 'user' }
entities:
User:
class: App\Entity\User
label: 'Benutzer'
password_encoding: { algorithm: 'bcrypt', cost: 12 }
form:
form_options: { validation_groups: ['Default'] }
fields:
- { type: 'group', icon: 'address-card', label: 'Informationen', css_class: 'col-lg-6' }
- username
- email
- { type: 'group', icon: 'user-shield', label: 'Rechteverwaltung', css_class: 'col-lg-6' }
- { property: 'is_active', type: 'checkbox' }
- { property: 'roles', type: 'choice', type_options: { multiple: true, choices: { 'ROLE_USER': 'ROLE_USER', 'ROLE_ADMIN': 'ROLE_ADMIN' } } }
- { type: 'group', icon: 'user-lock', label: 'Passwort', css_class: 'col-lg-6' }
- { property: 'plainPassword', type: 'text', type_options: { required: false } }
I can now answer my own question:
Solution: I simply forgot to reference the controller in Easyadmin routes:
# config/routes/easy_admin.yaml
easy_admin_bundle:
resource: 'App\Controller\AdminController'
prefix: /admin
type: annotation
and here is the complete controller for everyone with the same question:
// src/Controller/AdminController.php
namespace App\Controller;
use App\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
class AdminController extends EasyAdminController
{
private $passwordEncoder;
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
private function encodeUserPlainPassword($user)
{
$plainPassword = $user->getPlainPassword();
if (!empty($plainPassword)) {
$encoded = $this->passwordEncoder->encodePassword($user, $plainPassword);
$user->setPassword($encoded);
}
}
public function persistEntity($user)
{
$this->encodeUserPlainPassword($user);
parent::persistEntity($user);
}
public function updateEntity($user)
{
$this->encodeUserPlainPassword($user);
parent::updateEntity($user);
}
}
I am facing a small problem I don't really know how to fix, I tried some solutions that I will mention later but still got nothing, it is an authentication problem, when trying to make my User authenticate.
Well I have a User entity like this :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ApiResource(normalizationContext={"groups"={"read"}})
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"read"})
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"read"})
*/
private $username;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"read"})
*/
private $email;
/**
* #ORM\Column(type="string", length=255)
*/
private $password;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Post", mappedBy="user")
* #Groups({"read"})
*/
private $posts;
public function __construct(){
$this->posts = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->name;
}
public function setUsername(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getRoles(){
}
public function getSalt(){
}
public function eraseCredentials(){
}
public function getPosts(): Collection
{
return $this->posts;
}
}
And here is my security.yaml file :
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
#in_memory: { memory: ~ }
database:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api
stateless: true
anonymous: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# 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: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
So I did this exactly like mentionned in the course but still get this 401 error, so I tried to make some changes on some files, it was proposed by others who faced same issue, I tried to add this lines in my .htaccess file :
# Sets the HTTP_AUTHORIZATION header removed by Apache
RewriteEngine On
RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
But still getting the same error, so I was willing to try an other solution mentionned here, but I am on Windows not linux so the answer does not match my needs.
I am stuck for almost a day, I need help people, any help would be much appreciated.
I had a similar issue and fixed it like this:
security:
firewalls:
api:
pattern: ^/login_check
json_login:
check_path: /login_check
access_control:
- { path: ^/api/login_check, roles: IS_AUTHENTICATED_ANONYMOUSLY
This is somewhat discussed on LexikJWTAuthenticationBundle GitHub issues board: here
If your problem is in production, it's maybe due to Apache Autorisation, check the answer here : https://stackoverflow.com/a/70787257/3556984
I'm using symfony 3.4 with DoctrineMongoDBBundle and LexikJWTAuthenticationBundle . I'm trying to create a user login which return JWT token. If i specify the username and password under in_memory provider, it returns the token but if i use a entity provider, it returns {"code":401,"message":"bad credentials"}.
Here is my security.yml
# To get started with security, check out the documentation:
# https://symfony.com/doc/current/security.html
security:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
encoders:
AppBundle\Document\User:
algorithm: bcrypt
cost: 12
providers:
webprovider:
entity:
class: AppBundle\Document\User
property: username
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/api/login
stateless: true
anonymous: true
provider: webprovider
form_login:
username_parameter: username
password_parameter: password
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
main:
anonymous: ~
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
#http_basic: ~
# https://symfony.com/doc/current/security/form_login_setup.html
#form_login: ~
access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
Here is my User class
<?php
// /AppBundle/Document/User.php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #MongoDB\Document(collection="users")
* #MongoDBUnique(fields="email")
*/
class User implements UserInterface
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\Field(type="string")
* #Assert\Email()
*/
protected $email;
/**
* #MongoDB\Field(type="string")
* #Assert\NotBlank()
*/
protected $username;
/**
* #MongoDB\Field(type="string")
* #Assert\NotBlank()
*/
protected $password;
/**
* #MongoDB\Field(type="boolean")
*/
private $isActive;
public function __construct()
{
var_dump("1");
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid('', true));
}
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getUsername()
{
var_dump($this->username);
return $this->username;
}
public function getSalt()
{
return null;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials()
{
}
}
Would really appreciate if someone could help, Thanks.
You should use FOS bundle components by extending Base model User calss in entity, better than using directly Implementing UserInterface, In your case problem could be your password is not getting encoded correct. It's better to encode using security.password_encoder. To more better understanding i am sharing an example to setup login and generating token.
Your security.yml should look like this
`# Symfony 3.4 security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
ROLE_API: ROLE_USER, ROLE_MEDIC, ROLE_STUDENT
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
in_memory:
memory: ~
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api_login:
pattern: ^/api/login
stateless: true
anonymous: true
form_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
require_previous_session: false
api:
pattern: ^/(api)
stateless: true #false (not assign cookies)
anonymous: ~
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
provider: fos_userbundle
access_control: .................`
Your User class should be like this:
// /AppBundle/Document/User.php
namespace AppBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use FOS\UserBundle\Model\User as BaseUser;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
/**
* #MongoDB\Document(collection="users")
* #MongoDBUnique(fields="email")
*/
class User implements BaseUser
{
/**
* #MongoDB\Id
*/
protected $id;
/**
* #MongoDB\Field(type="string")
* #Assert\Email()
*/
protected $email;
/**
* #MongoDB\Field(type="string")
* #Assert\NotBlank()
*/
protected $username;
/**
* #MongoDB\Field(type="string")
* #Assert\NotBlank()
*/
protected $password;
/**
* #MongoDB\Field(type="boolean")
*/
private $isActive;
public function __construct()
{
var_dump("1");
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid('', true));
}
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getUsername()
{
var_dump($this->username);
return $this->username;
}
public function getSalt()
{
return null;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials()
{
}
}
Now your Api tokenController.php to generate token and validate token
namespace \your_namespace\Api;
use FOS\RestBundle\Context\Context;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use AppBundle\Document\User;
# Below most of the components belongs to Nelmio api or Sensio or Symfony
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
class TokenController extends FOSRestController
{
/**
* #Rest\Post("/tokens", name="api_token_new",
* options={"method_prefix" = false},
* defaults={"_format"="json"}
* )
*
* #ApiDoc(
* section = "Security",
* description = "Get user token",
* parameters={
* {"name"="username", "dataType"="string", "required"=true, "description"="username or email"},
* {"name"="pass", "dataType"="string", "required"=true, "description"="password "},
* }
* )
*/
public function newTokenAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$username = $request->get('username');
/** #var User $user */
$user = $em->getRepository('AppBundle:User')->findOneBy(['email' => $username]);
if (!$user) {
throw $this->createNotFoundException();
}
if (!$user->isEnabled()){
throw $this->createNotFoundException($this->get('translator')->trans('security.user_is_disabled'));
}
$pass = $request->get('pass');
$isValid = $this->get('security.password_encoder')->isPasswordValid($user, $pass);
if (!$isValid) {
throw new BadCredentialsException();
}
$token = $this->get('lexik_jwt_authentication.encoder')->encode([
'username' => $user->getUsername(),
'id' => $user->getId(),
'roles' => $user->getRoles(),
'exp' => time() + (30 * 24 * 3600) // 30 days expiration -> move to parameters or config
]);
// Force login
$tokenLogin = new UsernamePasswordToken($user, $pass, "public", $user->getRoles());
$this->get("security.token_storage")->setToken($tokenLogin);
// Fire the login event
// Logging the user in above the way we do it doesn't do this automatically
$event = new InteractiveLoginEvent($request, $tokenLogin);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
$view = $this->view([
'token' => $token,
'user' => $user
]);
$context = new Context();
$context->addGroups(['Public']);
$view->setContext($context);
return $this->handleView($view);
}
/**
* #Rest\Post("/validate", name="api_token_validate",
* options={"method_prefix" = false},
* defaults={"_format"="json"}
* )
*
* #ApiDoc(
* section = "Security",
* description = "Get user by token",
* parameters={
* {"name"="token", "dataType"="textarea", "required"=true, "description"="token"},
* }
* )
*/
public function validateUserToken(Request $request)
{
$token = $request->get('token');
//get UserProviderInterface
$fos = $this->get('fos_user.user_provider.username_email');
//create PreAuthToken
$preAuthToken = new PreAuthenticationJWTUserToken($token);
try {
if (!$payload = $this->get('lexik_jwt_authentication.jwt_manager')->decode($preAuthToken)) {
throw new InvalidTokenException('Invalid JWT Token');
}
$preAuthToken->setPayload($payload);
} catch (JWTDecodeFailureException $e) {
if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
throw new ExpiredTokenException();
}
throw new InvalidTokenException('Invalid JWT Token', 0, $e);
}
//get user
/** #var User $user */
$user = $this->get('lexik_jwt_authentication.security.guard.jwt_token_authenticator')->getUser($preAuthToken, $fos);
$view = $this->view([
'token' => $token,
'user' => $user
]);
$context = new Context();
$context->addGroups(array_merge(['Public'],$user->getRoles()));
$view->setContext($context);
return $this->handleView($view);
}
if you face any problem then , let me know.
you need to add the provider before json_login tag this the case for me
provider: fos_userbundle
Hi everybody and I m sorry if the question is dumb. I am very new with Symfony (2.6) and my first project requires the User to authenticate from AD.
Whatever I do I keep getting: Authentication request could not be processed due to a system problem.
security.yml
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
chain:
providers: [fos_userbundle, fr3d_ldapbundle]
fr3d_ldapbundle:
id: fr3d_ldap.security.user.provider
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
fr3d_ldap: ~
form_login:
# check_path: fos_user_security_check
# login_path: fos_user_security_login
always_use_default_target_path: true
default_target_path: /main
provider: chain_provider
logout:
path: fos_user_security_logout
target: /login
anonymous: ~
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
config.yml
fr3d_ldap:
driver:
host: 192.168.137.200
port: 50000
username: DOMAIN\Admnistrator # Optional
password: pass_here # Optional
user:
baseDn: OU=HP,OU=MANA,DC=DOMAIN,DC=com
filter: (&(ObjectClass=Person))
attributes: # Specify ldap attributes mapping [ldap attribute, user object method]
- { ldap_attr: uid, user_method: setUsername }
- { ldap_attr: mail, user_method: setEmail }
Entity/User class
/**
* #ORM\Entity
* #ORM\Table(name="mdr_user")
*/
class User extends BaseUser implements LdapUserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
* Ldap Object Distinguished Name
* #ORM\Column(type="string", length=128)
* #var string $dn
*/
private $dn;
public function __construct()
{
parent::__construct();
if (empty($this->roles)) {
$this->roles[] = 'ROLE_USER';
}
}
public function setName($name) {
$this->name = $name;
}
/**
* {#inheritDoc}
*/
public function setDn($dn)
{
$this->dn = $dn;
}
/**
* {#inheritDoc}
*/
public function getDn()
{
return $this->dn;
}
}
if I dont implement LdapUserInterface, the DB authenticates fine but always if I use anything else other than mysql entries I get that error. Can you please help me with that ?
Appreciate it.
Try to do this on your loginAction()
\Doctrine\Common\Util\Debug::dump($this->getDoctrine()->getEntityManager()->find('AppBundle:User', 1));
You might possible see an error. This works for me.
What fixed this for me was adding:
implements UserInterface, \Serializable
to the end of my entity's class declaration then adding the required methods to the entity at the bottom:
/**
* #return null
*/
public function getSalt(){
return null;
}
/**
* #return array
*/
public function getRoles(){
return array('ROLE_USER');
}
public function eraseCredentials(){
}
/**
* #return string
*/
public function serialize(){
return serialize(array($this->id, $this->username, $this->password));
}
/**
* #param string $serialized
*/
public function unserialize($serialized) {
list($this->id, $this->username,$this->password) = unserialize($serialized);
}