Home » Php » php – Decrypt Laravel Password Reset Token

php – Decrypt Laravel Password Reset Token

Posted by: admin July 12, 2020 Leave a comment

Questions:

I am writing a test that ensures that the password reset functionality of my application works. The password reset system was created using the php artisan make:auth command. In order to make the test pass I need to automate a GET request to /password/reset/{$token} where $token is the value stored in the password_resets table. Laravel stores the token like this:

$2y$10$9grKb3c6.Toiv0kjUWbCUeT8Q8D.Fg2gZ/xDLGQUAkmdyHigmRkNW

but when Laravel sends the password reset email to the user, the reset token looks like this in the email:

382aa64567ecd05a774c2e4ebb199d3340a1424300707053354c749c10487594.

My GET request to /password/reset/$2y$10$9grKb3c6.Toiv0kjUWbCUeT8Q8D.Fg2gZ/xDLGQUAkmdyHigmRkNW fails due to the forward slash in the reset token. (Right after the ‘g2gZ’)

I tried using the helper function decrypt() but had no luck.

How can I convert the password reset token I pull from the password_resets table to match what Laravel sends to the user?

Not sure if this is relevant but I did upgrade my application from 5.3 to 5.4.

How to&Answers:

You can get token from closure used for additional checks passed to Notification’s assertSentTo method because $token is a public property of standard ResetPassword notification.

In your test:

Notification::fake();

$this->postJson('api/user/reset', ['email' => $user->email])
    ->assertStatus(200);

$token = '';

Notification::assertSentTo(
    $this->user,
    \Illuminate\Auth\Notifications\ResetPassword::class,
    function ($notification, $channels) use (&$token) {
        $token = $notification->token;

        return true;
    });

$this->postJson('api/user/resetting', [
    'email' => $user->email,
    'token' => $token,
    'password' => '87538753',
    'password_confirmation' => '87538753'
])
    ->assertStatus(200);

Answer:

The token stored in the password_resets table is hashed just like a normal password, so you can’t reverse it to get the original token.

I suggest that you use the log mail driver when running tests. Then the password reset email will be printed in plain text in the laravel log and you can grab the token from that.

Answer:

I don’t think you can, the hash that’s saved is a bcrypted value of a sha256 hash of a random 40 digit number. which means it’s not reversible just one way checkable.

Answer:

For testing the password reset functionality, I replace the generated token from the password_reset table with a new one.

The reset token is created with the createTokenRepository() method – laravel/framework/src/Illuminate/Auth/Passwords/PasswordBrokerManager.php

For hashing the created token, Laravel uses the make() method – laravel/framework/src/Illuminate/Hashing/BcryptHasher.php

public function test_it_should_reset_the_password()
{

    Mail::fake();

    $user = factory(App\User::class)->create();

    $response = $this->json('POST', 'api/password/email',
                    [
                        'email' => $user->email
                    ]);
    $response->assertStatus(202);

    Mail::hasSent($user, ResetPassword::class);

    // Since we don't know the emailed token from 
    // the previous JSON call, we're
    // gonna replace the token with a new one
    $token = hash_hmac('sha256', Str::random(40), $user);
    DB::table('password_resets')
            ->where('email', $user->email)
            ->update([
                'token' => password_hash($token, PASSWORD_BCRYPT, ['cost' => '10'])
            ]);

    $response = $this->json('POST', 'api/password/reset', [
                    'email'                 => $user->email,
                    'password'              => 'new_user_password',
                    'password_confirmation' => 'new_user_password',
                    'token'                 => $token
                ]);
    $response->assertStatus(202);

    $response = $this->json('POST', 'api/login',
                    [
                        'email' => $user->email,
                        'password' => 'new_user_password'
                    ]);
    $response->assertStatus(202);
    // check for JWT token
    $response->assertJson(['token' => true]);

}