Kasey E
Kasey E

Reputation: 21

Symfony4 custom password encoder

I am upgrading an old application to use Symfony (v4) for the back end and I am stuck trying to get authentication functioning. My database stores hashed passwords and salts for the user, passwords used the crypt php function. I am also using the lexik JWT bundle in an attempt to return a token to the front end.

I cannot get a response other than "bad credentials" from symfony/lexik.

I think my problem is rooted in the encoders portion, I tried to implement a custom password encoder because crypt uses a modified DES algorithm according to the php docs and simply using the following in my security.yaml does not work.

encoders:
App\Entity\DB_1\User:
    algorithm: DES

Here is my complete security.yaml

security.yaml

security:
encoders:
    App\Entity\DB_1\User:
        id: 'App\Security\MyPasswordEncoder'

providers:
    entity_provider:
        entity:
            class: App\Entity\MetallicBonds\User
            property: username

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    login:
        pattern:  ^/login
        stateless: true
        anonymous: true
        json_login:
            check_path: /login_check
            success_handler: lexik_jwt_authentication.handler.authentication_success
            failure_handler: lexik_jwt_authentication.handler.authentication_failure

    register:
        pattern:  ^/register
        stateless: true
        anonymous: true

    api:
        pattern:  ^/test
        stateless: true
        anonymous: false
        provider: entity_provider
        guard:
            authenticators:
                - lexik_jwt_authentication.jwt_token_authenticator

access_control:
    - { path: ^/login_check, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/test, roles: IS_AUTHENTICATED_FULLY }

and my custom password encoder MyPasswordEncoder.php

namespace App\Security;

use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;

class MyPasswordEncoder extends BasePasswordEncoder implements  
PasswordEncoderInterface
{
private $ignorePasswordCase;

public function __construct($ignorePasswordCase = false)
{
    $this->ignorePasswordCase = $ignorePasswordCase;
}

public function encodePassword($raw, $salt)
{
    // TODO: Implement encodePassword() method.
    return crypt($raw,$salt);
}

public function isPasswordValid($encoded, $raw, $salt)
{
    // TODO: Implement isPasswordValid() method.
    if ($this->isPasswordTooLong($raw)) {
        return false;
    }

    try {
        $pass2 = $this->encodePassword($raw, $salt);
    } catch (BadCredentialsException $e) {
        return false;
    }

    if (!$this->ignorePasswordCase) {
        return $this->comparePasswords($encoded, $pass2);
    }

    return $this->comparePasswords(strtolower($encoded), strtolower($pass2));
}
}

I would like to use the currently stored passwords, at least for the moment, so the change from the old system to the new will be smooth for the end users. Has anyone had success using a custom password encoder with Symfony 4?

I should note that when I place an xdebug breakpoint in MyPasswordEncoder.php, it never stops the application, is there somewhere else the class needs to be registered for Symfony to use it?

Upvotes: 1

Views: 4408

Answers (1)

ureyni
ureyni

Reputation: 71

this is my security.yaml security: encoders: App\Entity\User: id: 'App\Security\Encoder\MyCustomPasswordEncoder'

this is my code

namespace App\Security\Encoder;


use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;

class MyCustomPasswordEncoder extends BasePasswordEncoder
{

private $algorithm;
private $encodeHashAsBase64;
private $iterations;

/**
 * @param string $algorithm          The digest algorithm to use
 * @param bool   $encodeHashAsBase64 Whether to base64 encode the password hash
 * @param int    $iterations         The number of iterations to use to stretch the password hash
 */
public function __construct(string $algorithm = 'sha1', bool $encodeHashAsBase64 = false, int $iterations = 0)
{
    $this->algorithm = $algorithm;
    $this->encodeHashAsBase64 = $encodeHashAsBase64;
    $this->iterations = $iterations;
}

/**
 * {@inheritdoc}
 */
public function encodePassword($raw, $salt)
{
    if ($this->isPasswordTooLong($raw)) {
        throw new BadCredentialsException('Invalid password.');
    }

    if (!in_array($this->algorithm, hash_algos(), true)) {
        throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
    }

    //$salted = $this->mergePasswordAndSalt($raw, $salt);
    $salted = $salt.$raw;
    $digest = hash($this->algorithm, $salted, true);

    // "stretch" hash
    for ($i = 1; $i < $this->iterations; ++$i) {
        $digest = hash($this->algorithm, $digest.$salted, true);
    }

    return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}

/**
 * {@inheritdoc}
 */
public function isPasswordValid($encoded, $raw, $salt)
{
    return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
}

}

Code is correctly run. old code is symfony 1.4.

Upvotes: 1

Related Questions