Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

larel_b's avatar

How Do You Send A First Time User A Password Reset Link? (

I followed @bashy on this post: https://laracasts.com/discuss/channels/laravel/reset-password-manually-without-email

I am generating a token like this:

$reset_token = hash_hmac('sha256', Str::random(40), $user);

I am inserting a record in the password_resets table like this:

    DB::table('password_resets')->insert([
       'email' => $request->email,
        'token' => $reset_token,
        'created_at' => Carbon::now(),
    ]);

The user gets an e-mail sent through a custom notification with the correct token (it matches on the link and in the hidden input field on the view).

I keep getting this after following the link, filling in the form, and clicking on reset password:

This password reset token is invalid.

I have also tried this to generate the token (same error):

$reset_token = strtolower(str_random(64));

0 likes
27 replies
Snapey's avatar

Why not just use a random string. This way you are not getting any unsafe characters in the token.

There should be no good reason to use the user in the token.

$reset_token = str_random(40);
larel_b's avatar

@Snapey Thanks for the fast reply. I tried that as I mentioned at the bottom of the post.

Snapey's avatar

And you are sending the same token to the user? Have you verified it by looking in your password_reset table and compare it to the url in the email.

larel_b's avatar

@Snapey Yep, it is exactly the same and the same in the hidden input field of the view.

Snapey's avatar

Well it only does a where statement on the reset table.

Try that?

select * from password_resets where email='email@here' and token='token here'

larel_b's avatar

@Snapey The select query works just fine and it finds the entry used in the link.

Snapey's avatar

Ok so sorry, I stand corrected.

The password reset does contain a hashed token. It is created with

   public function createNewToken()
    {
        return hash_hmac('sha256', Str::random(40), $this->hashKey);
    }

but I'm having trouble understanding where $this->hashkey comes from. The original code from @bashy says $user but is that an object or id?

Snapey's avatar

Not only that but the token created above seems to be hashed again;

protected function getPayload($email, $token)
{
    return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon];
}
larel_b's avatar

Yeah, I traced it back to the PasswordBrokerManager in vendor/laravel/framework/src/Illuminate/Auth/Passwords.

Look in the createTokenRepository method.

Snapey's avatar

Just typed loads of code and got blocked by the Cloudflare again. Pissed off this keeps happening...

larel_b's avatar

I'm really sorry that happend to you.

larel_b's avatar

Thanks for that! What is the context of this in $this->app?

Snapey's avatar

Its getting the key from the config (which in turn comes from the .env)

so the same as $key = config('app.key');

Snapey's avatar
Snapey
Best Answer
Level 122

Not sure using notifications.

    protected function registered(Request $request, $user, $reset_token) {
        $user->notify(new UserRegisteredNotification($user, $reset_token));  // no need to pass user to the notification
    }

Notification

public $reset_token;

public function __construct($reset_token)
{
    $this->reset_token = $reset_token;
}
public function toMail($notifiable) {
        $pw_reset = DB::table('password_resets')->where('email', $this->user->email)->first();
        return (new MailMessage)
            ...
            ->action('Reset Password', url(config('app.url').route('password.reset', $this->reset_token, false)))  // get the token from this object
            ...
    }
1 like
Snapey's avatar

I know you are getting close to cracking it, but TBH I don't like this as an option.

Password reset links are meant to have a very limited life but you are sending it in response to a new user being created. Does the user know they are being created or is someone else doing it for them (like an invite). If so, the token may well have expired before they deal with it

I would probably rather send them to the password reset page so they request a new token.

BezhanSalleh's avatar

i would show the reset password for the new registered user after he/she is authenticated (or verified their email if verification is involved) for the first time and after that business as usual.

Snapey's avatar

This does both. If they get the reset token then they have verified their email

BezhanSalleh's avatar

yeah, but if you are verifying the email then you send the user to the reset page and after reset to the login page. and you probably have a change password option for the authenticated user. for a first time user seems lots of hoop to jump over.

My way Verify->Authenticate->Reset by leveraging the change password option if exist->boom!

larel_b's avatar

@Snapey There is only 1 admin - the site owner - this person can create accounts without passwords - the new user will get an email link to set/reset the password.

BezhanSalleh's avatar

in this case on creation send an email with their username and system generated password and when a user is authenticated using their system generated password that's the first time show the reset password form. Et Voila!

larel_b's avatar

@BezhanSalleh Initially, I was going to do it that way, but I didn't want to send a plain text password through email. Do you think you can help me accomplish this in the way I intend?

Everything works, but I'm getting this password reset token is invalid when submitting the reset form.

larel_b's avatar

@Snapey Thanks I voted your answer as the correct one. I did it like this before, but I wasn't hashing the token correctly.

I used this in the RegisterController:

public $reset_token;

    $this->reset_token = hash_hmac('sha256', Str::random(40), $key);
    DB::table('password_resets')->insert([
       'email' => $request->email,
        'token' => Hash::make($this->reset_token),
        'created_at' => Carbon::now(),
    ]);

Then I passed the token to my notification class like so:

protected function registered(Request $request, $user) { $user->notify(new UserRegisteredNotification($user, $this->reset_token)); }

In the Notification class:

public $token;

public function __construct($user, $reset_token) {
    $this->user = $user;
    $this->token = $reset_token;
}

From there, I send the e-mail to the user with a link to

url(config('app.url').route('password.reset', $this->token, false))

It all works now!

Snapey's avatar

Good job

Just one thing I tried to call out in the code.

if you do $user->notify(notificationClass) then its the user that is being notified and is the $notifiable in the notification. You don't need to send the user again as a parameter to the class.

At least thats how I think it works.

larel_b's avatar

@Snapey My UserRegisteredNotification is called as a class like this:

$user->notify(new UserRegisteredNotification($user, $this->reset_token));

Notice that the reference to the user is inside the calling class and if you don't pass it in, then you have to get the reference some other way. This appears to be how it's done in the Laravel docs too. In my notification class, I'm actually using the user object to print details in the e-mail. I just didn't post the code because it wasn't relevant to the problem.

Thanks again for the help.

Snapey's avatar

But when you do user-notify() you are using the notifiable trait added to the User model

In your toMail class you accept a variable $notifiable. ...this is the user

In any case. In your notification class you pass the user into the constructor but you don't save it

Please or to participate in this conversation.