Invalide credentials JWT Symfony 6 - symfony

Having searched long and hard to understand my problem, I could see that the subject had been discussed a lot. But none of the solutions I came across helped me to understand and solve my problem.
I installed JWT on a Symfony 6 REST api I'm developing. I set up the user creation. I now want to be able to generate a JWT token with my user. But the endpoint returns :
{ "code": 401, "message": "Invalid credentials." }
And since then, I don't understand what is wrong with it. I went to see where it was going in the vendors hoping to understand, but I stop at the first dispatch() in the executeAuthenticator() method of the AuthenticatorManager class.
My sources are :
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\Personnage\Domain\Entity\User:
algorithm: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/api/login
stateless: true
json_login:
username_path: login
#password_path: password
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
# 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: ^/api/login, roles: PUBLIC_ACCESS }
- { path: ^/create-account, roles: PUBLIC_ACCESS }
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
path: /connexion
methods: ['POST']
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600
user_identity_field: login
I also put the creation of my user, in case the problem comes from there. I don't use Symfony User (no doctrine mapping and other services, I don't use the "magic" of the framework, which can sometimes impact details). It impacted me for the generation of the hashed password. And I wonder if it is not the hash of my password that is the problem.
[...]
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Uid\Uuid;
final class CreateAccount
{
public function __construct(
private UserRepository $repository,
private UserPasswordHasherInterface $hasher
) {}
public function __invoke(string $login, string $mail, string $password, string $genre): User
{
$hash = $this->hasher->hashPassword(
(new InMemoryUser($login, $password)),
$password
);
$user = new User(
Uuid::v4(),
$login,
$mail,
$hash,
$genre,
0,
0,
0,
0,
Uuid::v4(), //Todo : replace with real Uuid of city
null,
0,
2,
5,
new \DateTimeImmutable(),
new \DateTimeImmutable()
);
($this->repository)->create($user);
return $user;
}
}
User.php
<?php
namespace App\Personnage\Domain\Entity;
use DateTimeImmutable;
use Symfony\Component\Uid\Uuid;
class User
{
public function __construct(
private Uuid $uuid,
private string $login,
private string $mail,
private string $password,
private string $genre,
private int $level,
private int $intellect,
private int $strength,
private int $charisma,
private Uuid $city,
private ?Uuid $profession,
private float $money,
private int $hunger,
private int $shape,
private DateTimeImmutable $created,
private DateTimeImmutable $lastConnexion
) {}
public function getUuid(): Uuid
{
return $this->uuid;
}
public function getLogin(): string
{
return $this->login;
}
public function getMail(): string
{
return $this->mail;
}
public function getPassword(): string
{
return $this->password;
}
public function getGenre(): string
{
return $this->genre;
}
public function getLevel(): int
{
return $this->level;
}
public function getIntellect(): int
{
return $this->intellect;
}
public function getStrength(): int
{
return $this->strength;
}
public function getCharisma(): int
{
return $this->charisma;
}
public function getCity(): Uuid
{
return $this->city;
}
public function getProfession(): ?Uuid
{
return $this->profession;
}
public function getMoney(): float
{
return $this->money;
}
public function getHunger(): int
{
return $this->hunger;
}
public function getShape(): int
{
return $this->shape;
}
public function getCreated(): DateTimeImmutable
{
return $this->created;
}
public function getLastConnexion(): DateTimeImmutable
{
return $this->lastConnexion;
}
}
Thank you for helping me to understand :)

The particularity of my development is that I do not use the Doctrine ORM. I only use it for database connection and queries via DBal directly.
My problem is solved with the creation of a custom provider by following this documentation: https://symfony.com/doc/current/security/user_providers.html#creating-a-custom-user-provider
And adding to my User entity the interfaces :
Symfony\Component\Security\Core\User\UserInterface
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface

Related

Authenticate anonymously with roles

I have an Authenticator that needs to authenticate the user anonymously, but include a role. I do this by overriding createAuthenticatedToken in the Authenticator:
class ClientAuthenticator extends AbstractGuardAuthenticator
{
// supports(), getCredentials(), all working
public function getUser($credentials, UserProviderInterface $userProvider)
{
return new SessionUser;
}
// Return an anonymous user with the client role
public function createAuthenticatedToken(UserInterface $user, $providerKey)
{
return new AnonymousToken(
'Ynpir6i', // <-- here's the issue (the $secret param)
'anon.',
['ROLE_CLIENT_GUEST']
);
}
}
This works wonderfully -- when I hard-code the "secret" parameter of AnonymousToken.
I cannot figure out how to get this secret dynamically though. It is not the "secret" parameter provided in parameters.yml (aka %kernel.secret%).
I only got the secret I'm using now by dumping it out when it's set in AnonymousAuthenticationListener. I've looked at that service's configuration and I don't see it set at all.
What is this secret parameter, and how can I inject it into my Authenticator?
Or, is there a better way to add roles to an anonymous token that was authenticated a specific way?
That parameter may be set to a known value in security.yml:
security:
firewalls:
main:
anonymous:
secret: '%secret%'

How to use Symfony 4.1 Security / Authentication Providers without Doctrine / ORM

In my current app, we've chosen not to use Doctrine or an ORM. I'm attempting to use Symfony 4.1's authentication system to log people in. What I'd like to do is use PDO directly to fetch users from the database.
I'm following this guide: http://symfony.com/doc/current/security/custom_provider.html. In security.yaml, I've created a new provider entry and added it to the firewall:
providers:
db_provider:
id: App\Utility\Security\UserAuthenticationProvider
encoders:
App\Utility\Security\UserAuthenticationProvider: bcrypt
firewalls:
main:
pattern: ^/
form_login:
login_path: login
check_path: check_login
default_target_path: user
anonymous: ~
provider: db_provider
And I've created UserAuthenticationProvider:
class UserAuthenticationProvider implements UserProviderInterface {
private $config;
private $userDAO;
public function __construct() {
$this->config = new Config();
$this->userDAO = new MySqlUserDAO($this->config);
}
public function loadUserByUsername($username) {
$user = $this->userDAO->getUserByUsername();
...
return $user;
}
public function refreshUser(UserInterface $user) {
...
}
public function supportsClass($class) {
...
}
My userDAO returns an object that implements UserInterface.
So when I got to my route /login, I get my login form. However, no users are loaded from the database. I can see that my UserProviderInterface gets created (by using dump in the constructor), but loadUserByUsername does not.
Do I need to implement something else that uses my UserAuthenticationProvider and calls loadUserByUsername?
Is there perhaps a better way to do authentication in Symfony without using Doctrine?
UPDATE
I found this guide which is older but has a bit more detail / context.
I've changed my classes / configs like so (edited for brevity):
#security.yaml
security:
providers:
db_provider:
id: database_user_provider
main:
pattern: ^/
form_login:
provider: db_provider
login_path: login
check_path: check_login
default_target_path: do_some_stuff
.
#services.yaml
services:
database_user_provider:
class: App\Utility\Security\DatabaseUserProvider
.
class DatabaseUser
implements
UserInterface,
EquatableInterface
{
protected $user;
public function getUser(): User {
return $this->user;
}
public function setUser(User $user): void {
$this->user = $user;
}
public function getRoles() {
return array("ROLE_USER");
}
public function getPassword() {
return $this->getUser()->getPassword();
}
public function getUsername() {
return $this->getUser()->getUsername();
}
}
.
class DatabaseUserProvider implements UserProviderInterface {
private $config;
private $userDAO;
public function __construct() {
$this->config = new Config();
$this->userDAO = new MySqlUserDAO($this->config);
}
public function loadUserByUsername($username): UserInterface {
$user = $this->userDAO->getUserByUsername($username);
$dbUser = new DatabaseUser();
$dbUser->setUser($user);
return $dbUser;
}
public function refreshUser(UserInterface $user) {
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class) {
return DatabaseUser::class === $class;
}
}
So what each file is doing (in a nutshell is):
services.yaml: naming my class DatabaseUserProvider to the service name database_user_provider for use in security.yaml.
security.yaml: setting database_user_provider to the alias db_provider and adding that as the provider on the main firewall.
DatabaseUser: my 'entity' class to represent my user from the database. I have a dumb User class that just has a few properties (username, password) and getters / setters for those. It is set as a property for the DatabaseUser object.
DatabaseUserProvider: Loads my DatabaseUser objects from the database using the DAO and returns them. (more specifically, the DAO returns a user, which is added to a new DatabaseUser object, which is returned).
The DAO simply runs a sql query to get a single result from the user table. it then takes this result and populates a User Value Object and returns it.
Results
When I was using Doctrine (and following this guide for loading users from the database and this one for a login form), the login route on the SecurityController would handle both rendering of the form and processing of the login request. Somehow, Symfony / Doctrine was smart enough to automatically (using a listener maybe???) load the appropriate user entity from the DB and authenticate them against the password they provided (and then set the user token and redirect them to the page they were trying to access.)
When I bypass doctrine (and use my own DAOs) and the classes above, authentication still does not occur. I can see that an instance of DatabaseUserProvider is being created (by dumping some vars in the constructor), and the Security tab of the profilier shows database_user_provider as the provider. But that seems to be as far as it gets.
Question
It seems to me that DatabaseUserProvider::loadUserByUsername should be the next thing that should happen. From where does this method get called from? Do I need to be passing the username into the constructor and kick it off from there? Should I be using this class as a service in my Controller and call the method manually from the controller (something I did not have to do when using doctrine - none of this logic was in the controller)?

Symfony 3.4 - new security provider

I try add provider.What I'm doing wrong.
I have entity class, and I would like to extend this class
namespace AppBundle\Service;
use AppBundle\Entity\Pracownik;
use Symfony\Component\Security\Core\User\UserInterface;
class Userek extends Pracownik implements UserInterface
{
public function getUsername()
{
return $this->getLogin();
}
public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
}
public function getPassword()
{
return $this->getLogin();
}
public function getRoles()
{
return array('ROLE_PRACOWNIK');
}
public function eraseCredentials()
{
}
public function isSuperAdmin()
{
return false;
}
}
in security.yml i add
encoders:
AppBundle\Service\Userek: plaintext
providers:
pracownik_db_provider:
entity:
class: AppBundle\Service\Userek
property: login
firewalls:
main:
pattern: ^/
form_login:
provider: pracownik_db_provider
login_path: /login
check_path: /login_check
logout:
path: /logout
target: /login
When I try login, i got message:
The class 'AppBundle\Service\Userek' was not found in the chain configured namespaces AppBundle\Entity, FOS\UserBundle\Model"
If I implements Class Pracownik adding methods from the implemented class without creating an additional new class extending the Pracownik class - works
Try to move your Userek class to the AppBundle\Entity\ folder, because Doctrine expects to find model classes on that folder Entity\.
Just move AppBundle\Service\Userek.php to AppBundle\Entity\Userek.php and come back if you still get the error message.
If you want Doctrine use another folder to store your model classes or entities, you can find some help to do that here or on the full documentation Doctrine configurations.

How to migrate users passwords to Symfony FOSUserBundle table

I have an old website which I want to migrate to Symfony2 and use the FOSUserBundle.
My 'old' website's database stores encrypted passwords as follows:
sha1(\"$salt1$plain_text_password$salt2\")
However, I've not done this before and am not sure on how to go about doing it. Is my only option to somehow configure FOSUserBundle to use the same encryption as the old website? If so, where would I do this?
You can create a custom password encoder and override BasePasswordEncoder ::isPasswordValid() add your logic in it
example
class CustomPasswordEncoder extends BasePasswordEncoder
{
public function encodePassword($raw,$salt){
list($salt1,$salt2) = explode(",",$salt);
return sha1($salt1.$raw.$salt2); // your logic here
}
public function isPasswordValid($encoded,$raw,$salt)
{
return $this->comparePasswords(
$encoded,$this>encodePassword($raw,$salt));
}
}
make this class a service
service.yml
services:
custom-password-encoder:
class: path\to\CustomPasswordEncoder
and add this on your security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: {id: custom-password-encoder}
you also need to change User::getSalt() to return the two salts separated by comma
example
Class User extends BaseUser
{
public function getSalt()
{
return "salt1,salt2";
}
}
Snippet for Magento migration password logic.
<?php
namespace AppBundle\Utils;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
class CustomPasswordEncoder extends BasePasswordEncoder
{
public function encodePassword($raw, $salt)
{
$salt2 = base64_encode($salt.uniqid());
// logic from magento
return md5($salt2.$raw).":".$salt2;
}
public function isPasswordValid($encoded, $raw, $salt)
{
// magento logic
$hashArr = explode(':', $encoded);
$hashToValidate = md5($hashArr[1] . $raw);
return $this->comparePasswords(
$hashArr[0], // first piece of password
$hashToValidate // $salt.$password md5 hash
);
}
}

security.yml access_control match query param

I want to secure all urls that have the ?preview=true query string applied.
The following unfortunately does not work. I guess the request matcher looks at pathInfo only.
# app/config/security.yml
access_control:
- { path: (?|&)preview=true, role: ROLE_ADMIN }
Any hints are much appreciated.
Im pretty sure that access control only looks at the hierarchical part and not the query string. This is because query parameters are not meant to determine content, only to be filters on content. You will likely need to rework your URL structure to match these.
Solved it myself. As #Chausser said access control via query params is not possible in security.yml, so I've created a voter for that.
// src/Your/Bundle/Security/Voter/PreviewAccessVoter.php
namespace Your\Bundle\Security\Voter;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* voter that denies access if user has not the required role
* to access urls with preview query param set true
*/
class PreviewAccessVoter implements VoterInterface
{
protected $requestStack;
protected $requiredRole;
public function __construct(RequestStack $requestStack, $requiredRole)
{
$this->requestStack = $requestStack;
$this->requiredRole = $requiredRole;
}
public function supportsAttribute($attribute)
{
return true;
}
public function supportsClass($class)
{
return true;
}
public function vote(TokenInterface $token, $object, array $attributes)
{
if(filter_var($this->requestStack->getCurrentRequest()->query->get('preview'), FILTER_VALIDATE_BOOLEAN))
{
foreach($token->getRoles() as $role) {
if($this->requiredRole === $role->getRole()) {
return VoterInterface::ACCESS_GRANTED;
}
}
return VoterInterface::ACCESS_DENIED;
}
return VoterInterface::ACCESS_ABSTAIN;
}
}
Add voter as (hidden) service
# src/Your/Bundle/Resources/config/services.yml
# preview voter that denies access for users without the required role
security.access.preview.voter:
class: Your\BundleBundle\Security\Voter\PreviewAccessVoter
arguments: [ #request_stack , ROLE_ADMIN ]
tags: [ { name: security.voter } ]
public: false
Changing the Access Decision Strategy
# app/config/security.yml
access_decision_manager:
strategy: unanimous # can be: affirmative, unanimous or consensus
see: http://symfony.com/doc/current/cookbook/security/voters.html as a reference
https://github.com/symfony/Security/blob/master/Core/Authorization/Voter/RoleVoter.php might also be helpful

Resources