Cakephp 4 hashed password only work for 24 hours

Hi, I followed the steps from the cookbook and everything went fine. But after every max 24 hours, the login doens’t work for me.

I am not able to check, if something with my server went wrong, because I don’t know, how to check the differences of the hashcodes.

Am I the only one, who work with Cakephp 4 and hashed passwords and have this problem?

Without code or anything its hard to say. I can say I haven’t had any difficulty though.

If I had to guess it may be an expired cookie you were relying on, and maybe some browser setting which was drawing from the cache. 24 hours really does smell like an expired cookie though! Try accessing the site from different devices and browsers to see if its the same everywhere.

I’d also suggest building the CMS demo Content Management Tutorial - 4.x all the way through, and see if that stops working after 24 hours. In which case, you may need to be looking higher up at your stack: the PHP version, the server version (eg Apache), file level security & firewalls.

What does “hashed password only works for 24 hours” mean? After 24 hours you are no longer able to log in any more using that password? Or you log in and then 24 hours later it’s no longer remembering that you had logged in and asking you to do so again?

I rebuild the Tutorial and here is my Code:

Application.php


use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Identifier\IdentifierInterface;
use Authentication\Middleware\AuthenticationMiddleware;

class Application extends BaseApplication implements AuthenticationServiceProviderInterface
{

public function bootstrap(): void
{

$this->addPlugin(‘Authentication’);

}

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{

->add(new AuthenticationMiddleware($this))

}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
‘unauthenticatedRedirect’ => ‘/myProject/users/login’,
‘queryParam’ => ‘redirect’,
]);
$fields = [
IdentifierInterface::CREDENTIAL_USERNAME => ‘usr_name’,
IdentifierInterface::CREDENTIAL_PASSWORD => ‘usr_password’
];

    // Load identifiers, ensure we check email and password fields
    /*$authenticationService->loadIdentifier('Authentication.Password', [
        'fields' => /*[
            'username' => 'usr_name',
            'password' => 'usr_password',
        ] $fields
    ]);*/

    // Load the authenticators, you want session first
    $authenticationService->loadAuthenticator('Authentication.Session');
    // Configure form data check to pick email and password
    $authenticationService->loadAuthenticator('Authentication.Form', [
        'fields' => /*[
            'username' => 'usr_name',
            'password' => 'usr_password',
        ]*/ $fields,
        'loginUrl' => '/adminHigh/users/login',
    ]);
    // // Load identifiers, ensure we check email and password fields
    $authenticationService->loadIdentifier('Authentication.Password', compact('fields'));

    return $authenticationService;
}

}

UserController

public function login() {

    $this->request->allowMethod(['get', 'post']);
    $result = $this->Authentication->getResult();     

    // regardless of POST or GET, redirect if user is logged in
    if ($result->isValid()) {       
        $redirect = $this->request->getQuery('redirect', [
                    'controller' => 'Courses',
                    'action' => 'index',
                ]);

        return $this->redirect($redirect);
       
    }
    // display error if user submitted and authentication failed
    if ($this->request->is('post') && !$result->isValid()) {
        $this->Flash->error(__('Invalid username or password'));
    }
}

User.php
protected function _setUsrPassword(string $password) : ?string
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
}

Everything works fine for about 24 hour, sometime more up to nearly 48 hours, and sometimes I get “Invalid password or Username” after less then 24 hours. I created a trigger, when my user table on the db is changing, but there is no changing…

So, the person’s hashed password in the database is still the same 24 hours later, but now it won’t let them log in any more?

Yes.
But today I found something out. I have a second project, it is the same but my test-enviroment.
It use the same database, and when I try to login there it worked and afterwards it workes also on my business enviroment…

I am so confused…

If the hashed password is still the same, then there’s no good reason I can think of why it would stop being able to validate the provided password during the login process. If you’re using an old hash function with a salt value, and that salt is changing, that would do it. But otherwise…

As Zuluru says, changing the salt/algorithm is the only thing which could break it within Cake. As I don’t know the guts of Cake it never hurts to purge the cache, just in case!
bin/cake cache clear_all

Maybe also switching between http / https could break it perhaps? Like it’ll work via http:// but not https:// - so check that and use .htaccess (or equivalent) to force https.

Finally, check its not a password manager pulling the wrong password. Are you typing it every time? Also, remotely possible, a RTL language or accent characters + unicode in the password may make it inconsistent - and thus can’t even rely on copy/paste of the password as seen in clear text.

Good point about copy/paste. I’ve seen passwords fail because copy often grabs a space at the end of the highlighted text, and that will cause a password mismatch.

It shouldn’t, everything CakePHP uses password_hash and password_verify under the hood (source).
This means that the hash output will contain everything that’s needed:

  • The algorithm used
  • the salt
  • the cost
  • the hash itself

All password_verify does is take the first 3 parameters and runs them through the algorithm again then compares against the hash to see if they match.

Here’s some illustrative code to show what PHP does to create the hash:

$password = 'lamepassword';
$salt = 'lamesalt'; // Obviously this changes every time
$algo = 'sha256';
$rounds = 10;

function makeHash(string $data, string $salt, string $algo) {
  return hash($algo, $salt . $data);
}

$hash = $password;
for($round = 0; $round <= $rounds; $round++) {
  $hash = makeHash($password, $salt, $algo);
}

var_dump($algo . ':' . $rounds . ':' . $salt . ':' . $hash); // string(83) "sha256:10:lamesalt:75d640c3f13991f3932e1337c4878d87602abd01d56766da77fd0ff5b2cb936b"

Now for verification, it does the following (again, just for illustration):

$password = 'lamepassword';
$hashStr = "sha256:10:lamesalt:75d640c3f13991f3932e1337c4878d87602abd01d56766da77fd0ff5b2cb936b";

list($algo, $rounds, $salt, $expectedHash) = explode(':', $hashStr);

function makeHash(string $data, string $salt, string $algo) {
  return hash($algo, $salt . $data);
}

$hash = $password;
for($round = 0; $round <= $rounds; $round++) {
  $hash = makeHash($password, $salt, $algo);
}

var_dump($hash === $expectedHash); // bool(true)

If the user were to give the wrong password, the expected hash and the hash we get from the input password won’t match up.
The hash-string itself contains all data needed to do the verification, this is exactly to prevent cases where changing the algorithms in the code would prevent user login.

Developers can check whether the password needs a rehash using \Cake\Auth\DefaultPasswordHasher#needsRehash.

Additionally, salts should always be unique when hashing passwords.

  • Salts are “unique” per password (to combat the effectiveness of so-called “rainbow tables”).
  • Peppers are “unique” per app (but are rarely used when hashing passwords, atleast, in PHP).

This also shouldn’t matter as the only difference here is how the browser and server will communicate.
The data (and thus the password) will be the same, it’s just wrapped with some encryption so it is harder to intercept.
To CakePHP, the data (and thus the password) it’ll see will be basically the same.

Clearing the cache don’t help.

Today I found out, that the problem is the route:

$builder->connect(’/’, [‘controller’ => ‘Users’, ‘action’ => ‘login’]);

When I change it to:

$builder->connect(’/’, [‘controller’ => ‘Courses’, ‘action’ => ‘Index’]);

I will be redirected(because it is only accessable after the login) and my url is like:

“…/users/login?redirect=%2F” instead of “…/”

and now the login worked. It is a workaround which I can live with, but I don’t understand why this happen.

There is stuff in the authentication plugin that validates that the login is happening on a valid URL. I’ve butted heads with it on occasion, and still don’t quite entirely understand how it all works, but that might be what you’re running into.

Oooh, I remember now having a similar issue. I think the login page needed to be at the root of the website, it didn’t work nested in a sub-folder. I’ll dig around and check my notes / code, I may find the example where this happened, in which case I’ll edit this post. Whether that relates to the issue you had though, I cannot say. (I’ve also read it won’t work [well] if the Users table isn’t actually users and the fields in it aren’t email and password - just saying!)

None of this, though, explains the assertion that the login works just fine at first, and then stops working 24 hours later.

Yea, I’m guessing the original login took, but a later redirect - perhaps after a 24 hour timeout by config/app.cfg 'Session' => [ 'timeout' => 1440 ], stored in the cookie/session is where its falling apart.