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

bashy's avatar
Level 65

Reset password manually without email

Hi all,

I have an app where I would like to request users to set their password upon creation (by an admin). I have manually inserted a record to the password_resets with DB::table()->insert([]); but was wondering if anyone can think of a better way?

The link they get goes straight to the /password/reset/{token} route so it's pretty handy.

0 likes
29 replies
mstnorris's avatar

I have recently implemented a different approach albeit similar. On the users table, I've set a boolean field called must_reset_password and added Middleware to this effect.

When a User is created, the must_reset_password is set to true and when they try to login they are redirected to set a password.

I've also considered changing it from a boolean to a date field so that when a password is reset, a new date (maybe 90 days in the future) is selected and is that which is checked when a User tries to log in. If the date has passed, their password has expired.

I'd be interested to see your code though as it sounds like a clean and simple approach.

1 like
theUnforgiven's avatar

Nothing wrong with the way your doing it, stick with that :)

1 like
bashy's avatar
Level 65

Yeah I was thinking about doing something like that but thought it was a lot to do just to make someone set a password. Plus all the expire stuff and setting different fields true or false.

This is what I currently have (works for what I want)

Admin creates a user from just name/email and other optional fields

$user->password = str_random(20); // yes this is bcrypted in User model ;)
// do a save from $request->all() etc

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

$reset_token gets sent to new user email with link to { { route('password.reset.get', $reset_token) } }

And Laravel will handle the expire stuff and validation of it being set?

Similar to this part from where you would reset it by email?

$response = Password::sendResetLink($request->only('email'), function (Message $message) {
    $message->subject($this->getEmailSubject());
});
3 likes
mstnorris's avatar

That is a nice way to do it, and if you don't need the other stuff then I don't really see a need to change it.

bashy's avatar
Level 65

Yeah indeed :)

It's very app specific but bear in mind that the method above will use the token expire time set in config/auth.php. This default of 60 minutes is fine for the client since they will be making accounts when the user knows about it. It would probably expire too soon for anything general (people reading emails 24 hours later etc).

Thanks all!

1 like
bobbybouwmann's avatar

Really smart @bashy

I created my own activate method but it's doing almost the same thing as the password reset field. And like you mentioned, the expire time can be an issue for not active email users

1 like
bashy's avatar
Level 65

Really surprised there's not a trait or method to insert the reset token manually without sending an email. Just by a user collection.

@bobbybouwmann I was first making another route for it like /set-password/{token} but thought...wait a minute...I have a route already set for setting passwords!

Makten's avatar

@bashy I am trying to implement same functionality as yours above. - Admin creates user without password, user gets an email to create their own password. I however don't know exactly how and where I can implement your code to make this possible. Any help will be greatly appreciated.

rodrigo.pedra's avatar

I use a similar approach in one of my projects, there is no need to send an e-mail:

// PasswordController


    /**
     * Display the password reset view for the given token.
     *
     * @param  string $token
     *
     * @return Response
     */
    public function showResetForm( $token = null )
    {
        if (auth()->guest() && is_null( $token )) {
            return redirect('password/email');
        }

        if (auth()->check() && is_null( $token )) { 
             // user is logged in and has no token, in other words, he/she access this route by 
             // clicking a link pointing to "password/reset", so we generate a new token and save it 
             // to the password_resets table
            $token = \Password::getRepository()->create( auth()->user() );
        }

        return view( 'auth.passwords.reset' )->with( 'token', $token );
    }
2 likes
bashy's avatar
Level 65

@rodrigo.pedra You can't know what people need. Please don't suggest "there is no need to send an e-mail". Your method is quite different...

1 like
rodrigo.pedra's avatar

@bashy of course I can't.

I first understood you were seeking for a method to allow the user to reset their password without the need to send an e-mail. Reading again carefully I see that I misunderstood what you were asking, sorry for that, I was just trying to help.

aacook's avatar

@bashy - I'm trying a similar approach but Laravel doesn't seem to like the tokens I'm generating. In the sample auth views, on /password/reset/, I get this error: This password reset token is invalid.

For example, I have the URL: http://localhost:8000/password/reset/edcjmoygpi8rhbucn1v7azgxkcsvc7avipxyft5gmkz3nsobkl7r6g2zhavynk08?email=(my email)

The token edcjmoygpi8rhbucn1v7azgxkcsvc7avipxyft5gmkz3nsobkl7r6g2zhavynk08 is exactly what I see in the passwords_reset table.

Is this approach still working for you?

bobbybouwmann's avatar

@aacook It's better to create a new thread if you have a question ;)

Can you show us some code that you have? (in a new thread of course)

1 like
bashy's avatar
Level 65

@aacook You also need to have the token in the form when resetting, do you have that? It's for the POST route, not the GET :)

aacook's avatar

Should I really start a new thread? Seems fitting to comment here on the sample code itself. Happy to start one if necessary. I'm new here so don't really know the protocol.

So it seems like the bug comes down to Laravel / Carbon saying the tokens are expired when they're not. It looks like Carbon is using a different timezone than mysql. All of the tokens are coming back expired. In my case, Carbon::now() is giving a timestamp 3 hours ahead of the token expiration.

Curious, why doesn't Laravel just tell the user the token expired, rather than give a generic error for both invalid and expired tokens? Is this for security reasons?

aacook's avatar

Doh! Well, it turns out I wasn't properly inserting into the passwords_reset table - wasn't defining created_at:

  DB::table('password_resets')->insert( ['email' => $this->email, 'token' => $token] );

and should've been doing:

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

While investigating, I noticed Laravel encrypts the token. Unsure if this is necessary, but I updated my token to match what's in DatabaseTokenRepository->createNewToken()

$token = hash_hmac('sha256', Str::random(40), $this->hashKey);
bashy's avatar
Level 65

Have you set your timezone in config/app.php?

Ravenberg's avatar

✌ I know this thread is ancient, but I ran into a similar problem. I wanted to implement my own password reset mechanism, however with the email as identifier. Here is how I solved it;

class PasswordResetController extends Controller
{
    /**
     * @var Illuminate\Auth\Passwords\PasswordBroker 
     */
    private $passwordBroker;

    /**
     * PasswordResetController constructor.
     * @param Illuminate\Auth\Passwords\PasswordBroker $passwordBroker
     */
    public function __construct(Illuminate\Auth\Passwords\PasswordBroker $passwordBroker)
    {
        $this->passwordBroker = $passwordBroker;
    }

    public function requestPasswordReset(Request $request)
    {
        $request->validate([
            "email" => "required|email|exists:users,email"
        ]);

        $user = User::where("email", $request->email)->first();
        $result = $this->passwordBroker->createToken($user);

        return response($result);
    }
}

I like this solution because it takes away the maintenance from my app to the framework. I also like how it checks if there is an existing token record instead of simply adding a new one.

⚠️ Note that this solution only works when your User model implements the he `Illuminate\Contracts\Auth\CanResetPass contract. I like to see some more documentation on this though

larel_b's avatar

@bashy I know this is an old post, but I used your code for sending the password reset link and I get that "This password reset token is invalid" when attempting to reset the password. It works when generated by the framework.

bashy's avatar
Level 65

@larel_b Best to open a new thread and mention me or something :)

I think you have already!

Philword's avatar

@bashy

Hi!

I tried your method:

Accept Invitation:

public function accept($token)
    {
        // Look up the invite
        if (!$invite = Invite::where('token', $token)->first()) {
            //if the invite doesn't exist do something more graceful than this
            abort(404);
        }
        // find user
        $user = User::where('email', $invite->email)->first();

        $reset_token = strtolower(str_random(64));
        DB::table('password_resets')->insert([
            'email' => $user->email,
            'token' => $reset_token,
            'created_at' => Carbon::now(),
        ]);
        
    // delete the invite so it can't be used again
         $invite->delete();

    //send url on password reset
        Mail::to($user->email)->send(new PasswordCreate($reset_token));

        // temporary return
        return 'Good job! Invite accepted!';
    }

My Mail class:

class PasswordCreate extends Mailable
{
    use Queueable, SerializesModels;
    public $reset_token;
    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($reset_token)
    {
        $this->reset_token = $reset_token;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('[email protected]')
                    ->subject('Bchain Shares accout details')
                    ->view('emails.password');
    }
}

And view:

<p>Your accound has been created!</p>

<p>Follow the link below to set your password.</p>

<a href="{{ route('password.reset', $reset_token) }}">Click here</a> to set your password!

Everything works good except, that when it return me Password reset form, after enter email + pass+ pass conformation I got "This password reset token is invalid"

Token stored in database:

'token' => `ubqxhqwqtuzbu17dhmfeyq5ne4nhzbc82tba0lugwb7xzjmpm2gddysuvwgjblpt` 

Password Reset URL from Email:

http://127.0.0.1:8000/password/reset/ubqxhqwqtuzbu17dhmfeyq5ne4nhzbc82tba0lugwb7xzjmpm2gddysuvwgjblpt

What's the trick here? I guess it says like this, because token encrypted, but how to send correct url to reset password?

bashy's avatar
Level 65

@Philword The default password reset views for Laravel auth should work fine. If you're overriding them, you probably forgot to pass the token in the view (form data).

1 like
Philword's avatar

@bashy

No, it's there:

<input type="hidden" name="token" value="{{ $token }}">

More weird, that If user use regular password reset, it works fine.

bashy's avatar
Level 65

@Philword Oh my bad. Yeah something to do with the token value then? I've not had this issue but I didn't override it all like you have.

1 like
Cronix's avatar

Edit: nevermind, this isn't a csrf token

AliVeseli's avatar

Declare this function inside a helper file somewhere

function get_user_by_token($token){
    $records =  DB::table('password_resets')->get();
    foreach ($records as $record) {
        if (Hash::check($token, $record->token) ) {
           return $record->email;
        }
    }
}

Then Inside the reset.blade.php hide the from-group of the email and in the input email paste this !

value="{{ old('email') ?? get_user_by_token($token) }}"
Brunomasquio's avatar

Try this function, executing:

use App\Services\Password\ResetPassword;
app(ResetPassword::class)->getResetPasswordLink('[email protected]');

After declaring this class:

namespace App\Services\Password;

use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Password;

class ResetPassword extends Password
{
    use SendsPasswordResetEmails;

    public function getResetPasswordLink($email)
    {
        \Validator::make($array = ['email' => $email], [
            'email' => 'required|email',
        ])->validate();

        $broker = $this->broker();

        $user = $broker->getUser($array);
        $token = $broker->getRepository()->create($user);

        return url(
            route(
                'password.reset',
                [
                    'token' => $token,
                    'email' => $user->email,
                ],
                false
            )
        );
    }
}

Please or to participate in this conversation.