How to Implement Password Reset in Laravel

Editorial Note: We may earn a commission when you visit links on our website.

One of the most essential features any web application needs is a secure password reset system. If you’re looking to implement password reset in Laravel, you’ve come to the right place.

Whether you’re building a new application or adding authentication to an existing project, nobody wants frustrated users locked out of their accounts. Laravel makes it easy to implement this functionality with its built-in password reset features.

In this guide, I’ll walk you through implementing password reset in Laravel. We’ll cover the built-in system and how to create custom implementations for APIs and advanced use cases.

Let’s jump right in!

Understanding Laravel’s Built-In Password Reset System

Before diving into implementation, let’s understand how Laravel’s password reset works under the hood.

How Laravel Password Reset Works

Laravel provides a complete password reset system out of the box. Here’s how the flow works:

  1. User clicks “Forgot Password” and enters their email address.
  2. Laravel generates a unique token and stores it in the password_reset_tokens table.
  3. Upon submitting the new password, Laravel validates the token.
  4. If valid, Laravel will update the user’s password and delete the token.
Laravel password reset flow

The beauty of this system is that tokens are hashed for security and automatically expire after a set time (60 minutes by default).

Key Components

Laravel’s password reset functionality relies on several key components:

  • PasswordBroker: Manages token creation and validation.
  • Password Facade: Provides convenient methods for resetting passwords.
  • ResetPassword Notification: Sends the password reset email.
  • password_reset_tokens Table: Stores reset tokens.

Now that we understand how it works, let’s implement it.

How to Implement Password Reset in Laravel

Prerequisites

Before we get started, make sure you have:

  • Laravel 11.x or 12.x installed on your machine.
  • Basic understanding of Laravel routing and controllers.
  • A mail server or SMTP server configured. I’ll show you how to use SendLayer’s SMTP.
  • Database connection set up.
  • A code editor like VS Code.

You can check your Laravel version by running:

php artisan --version

If you don’t have Laravel installed yet, you can set up a new project with:

composer create-project laravel/laravel password-reset-demo

Tip: password-reset-demo is the name of the Laravel project. You can use any name you’d like.

Then navigate into the project directory using the command:

cd password-reset-demo

How to Set Up Password Reset Using Laravel Breeze

The quickest way to get password reset working is to use Laravel Breeze, which provides a complete authentication scaffolding.

Step 1: Install Laravel Breeze

Laravel Breeze is a minimal authentication starter kit that includes login, registration, password reset, and email verification. To install it, run:

composer require laravel/breeze --dev

You’ll be prompted to choose a stack. For this tutorial, I’ll use the Blade stack. You can simply run the command below to install with the Blade stack:

php artisan breeze:install blade

Next, install the frontend dependencies using the command below:

npm install && npm run dev

Note: npm run dev starts the Vite frontend development server. Please keep this server running to use the password reset forms.

Once the installation completes, run the command below to migrate your database:

php artisan migrate

This command creates all necessary database tables, including the password_reset_tokens table.

Step 2: Configure Your Email Settings

Password reset relies on email notifications, so you’ll need to configure your mail settings in the .env file.

I recommend using SendLayer for reliable email delivery. Here’s how to configure it:

MAIL_MAILER=smtp
MAIL_HOST=smtp.sendlayer.net
MAIL_PORT=587
MAIL_USERNAME=your_sendlayer_username
MAIL_PASSWORD=your_sendlayer_password
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

Important: Your MAIL_FROM_ADDRESS should use the domain you’ve authorized in SendLayer. For example, if you authorized example.com, use [email protected].

  • 200 Free Emails
  • Easy Setup
  • 5 Star Support

If you’re using SendLayer, you can get your SMTP credentials from your account dashboard under Settings » SMTP Credentials.

SendLayer SMTP credentials

Pro Tip: If you need more help, please see our tutorial on setting up email in Laravel.

Step 3: Verify the Password Reset Database Table

Let’s check that the password reset table was created correctly. The migration should look like this:

Schema::create('password_reset_tokens', function (Blueprint $table) {
    $table->string('email')->primary();
    $table->string('token');
    $table->timestamp('created_at')->nullable();
});

You can verify this by running:

php artisan migrate:status

The table stores three things:

  • email: The user’s email address
  • token: A hashed version of the reset token
  • created_at: Timestamp for token expiration

Step 4: Test the Password Reset Flow

With Breeze installed, you already have a working password reset system. Let’s test it:

Start your development server using the command:

php artisan serve

After that, open your browser and navigate to http://localhost:8000/login. Once there, click the Forgot your password? link.

Click forgot your password in Laravel

Then, enter a registered user’s email address and click the EMAIL PASSWORD RESET LINK button.

Click the send password reset link button

You should receive the message in the user’s inbox. Go ahead and click the Reset Password button to reset your password.

Click reset password

If the token is valid, you’ll be directed back to your Laravel application to enter your new password. The token will be added as a URL parameter in the reset password link.

Go ahead and enter and confirm your new password. Then click the RESET PASSWORD button to update your password.

Enter new password in Laravel

If everything works, congratulations! You’ve successfully implemented password reset in Laravel.

How to Implement Password Reset API for Modern Applications

If you’re building a mobile app, SPA, or headless CMS, you’ll need an API-based password reset system instead of traditional web forms. Here’s how to do it:

Step 1: Create API Routes

Start by creating a new api.php file in the routes folder. Then, add these routes to your routes/api.php:

<?php

use App\Http\Controllers\Api\PasswordResetController;

// Laravel reset password API routes
Route::post('/forgot-password', [PasswordResetController::class, 'forgotPassword']);
Route::post('/reset-password', [PasswordResetController::class, 'resetPassword']);

This PHP code defines the API routes that connect HTTP endpoints to the password reset controller methods we’ll create in step 3.

First, we import the PasswordResetController class so we can reference its methods in our route definitions.

Then we define two POST routes using Laravel’s Route::post() method. The /forgot-password endpoint maps to the forgotPassword method, which handles sending reset links.

The /reset-password endpoint maps to the resetPassword method, which handles the actual password change.

Step 2: Register API Routes

After defining the API routes, we’ll need to register them so Laravel can process the routes. Laravel 11+ requires explicit API route registration in bootstrap/app.php.

To do so, open the bootstrap/app.php file and add the line below to the return statement. Ideally, it should be placed below the web line.

api: __DIR__.'/../routes/api.php',

If you omit this line, your application will default to the routes defined in web.php.

Step 3: Create the API Controller

Now, we’ll need to create a new controller for API password reset:

php artisan make:controller Api/PasswordResetController

After that, open the Http/Controllers/Api/PasswordResetController file and replace its content with the snippets below:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class PasswordResetController extends Controller
{
    public function forgotPassword(Request $request)
    {
        $request->validate(['email' => 'required|email']);

        $status = Password::sendResetLink(
            $request->only('email')
        );

        if ($status === Password::RESET_LINK_SENT) {
            return response()->json([
                'message' => 'Password reset link sent to your email.'
            ], 200);
        }

        throw ValidationException::withMessages([
            'email' => [__($status)],
        ]);
    }

    public function resetPassword(Request $request)
    {
        $request->validate([
            'token' => 'required',
            'email' => 'required|email',
            'password' => 'required|min:8|confirmed',
        ]);

        $status = Password::reset(
            $request->only('email', 'password', 'password_confirmation', 'token'),
            function ($user, $password) {
                $user->password = Hash::make($password);
                $user->save();
            }
        );

        if ($status === Password::PASSWORD_RESET) {
            return response()->json([
                'message' => 'Password has been reset successfully.'
            ], 200);
        }

        throw ValidationException::withMessages([
            'email' => [__($status)],
        ]);
    }
}

Code Breakdown

In the code above, we define a Laravel API controller that handles password reset, including sending reset links and processing password changes.

First, we define the namespace and import the necessary Laravel classes. Here’s an overview of the classes we’re using: 

  • Controller: The base class.
  • Request: Handles HTTP data.
  • Password: Used for reset operations.
  • Hash: Handles secure password hashing.
  • ValidationException: For error handling.

The forgotPassword method validates that the request includes a valid email address. Then, uses Password::sendResetLink() to generate a unique token and send a reset email.

If successful, it returns a JSON confirmation; otherwise, it throws a validation exception with the error message.

The resetPassword method validates the token, email, and new password. It then calls Password::reset(), which verifies the token.

After that, it executes a callback that hashes the new password with Hash::make() and saves the user’s details.

Step 4: Password Reset Email Customization

By default, Laravel sends a branded password reset email template. Let’s go ahead and customize it to match your brand.

First, create a custom notification:

php artisan make:notification CustomResetPasswordNotification

Then update the notification class:

<?php

namespace App\Notifications;

use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class CustomResetPasswordNotification extends Notification
{
    public $token;

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

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        $url = url(route('password.reset', [
            'token' => $this->token,
            'email' => $notifiable->getEmailForPasswordReset(),
        ], false));

        return (new MailMessage)
            ->subject('Reset Your Password')
            ->line('You are receiving this email because we received a password reset request for your account.')
            ->action('Reset Password', $url)
            ->line('This password reset link will expire in 60 minutes.')
            ->line('If you did not request a password reset, no further action is required.');
    }
}

Finally, override the sendPasswordResetNotification method in your User model (app/Models/User.php):

use App\Notifications\CustomResetPasswordNotification;

public function sendPasswordResetNotification($token)
{
    $this->notify(new CustomResetPasswordNotification($token));
}

Your password reset emails will now use your custom template.

Step 5: Test the API

You can test your API endpoints using cURL or an API client like Postman. Here’s how to run a quick test using cURL:

# Send password reset link
curl -X POST http://localhost:8000/api/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

After requesting the password reset link, you should receive an email notification if the user’s email exists.

Testing API-based password reset flow in Laravel

Since this is an API route, there is no view to update the password. So the reset password button won’t work.

We’ll need to copy the Laravel password reset token. Then use it when making an API call to the reset password endpoint. Here’s an example command:

# Reset password
curl -X POST http://localhost:8000/api/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token":"your_token_here",
    "email":"[email protected]",
    "password":"newpassword123",
    "password_confirmation":"newpassword123"
  }'

Replace the "token" value with the actual token you received in the forgot password email. You should receive a successful response after sending the request.

{"message":"Password has been reset successfully."}%  

How to Customize Token Expiration Time

By default, password reset tokens expire after 60 minutes. You can change this in your config/auth.php file. Here’s how to update Laravel’s password reset expiration:

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_reset_tokens',
        'expire' => 30, // Change to 30 minutes
        'throttle' => 60,
    ],
],

The throttle option prevents users from requesting multiple reset emails within the specified timeframe (in seconds).

Troubleshooting Common Password Reset Issues

These are troubleshooting tips for Laravel password reset not working issues.

"This password reset token is invalid" Error

This is the most common error you’ll encounter. It can happen for several reasons:

  • The token has expired (default is 60 minutes)
  • The token has already been used
  • The email doesn’t match the token in the database
  • There’s a mismatch between the token in the URL and database

To resolve this issue:

  1. Check your token expiration setting in config/auth.php
  2. Verify the email address matches exactly (case-sensitive)
  3. Clear the password reset tokens table for testing:
php artisan tinker
DB::table('password_reset_tokens')->truncate();
  1. Make sure you’re passing the token correctly in your form

Password Reset Email Not Sending

If users aren’t receiving password reset emails, here’s what to check:

  1. Test your email configuration:
php artisan tinker
Mail::raw('Test email', function($msg) {
    $msg->to('[email protected]')->subject('Test');
});
  1. Check your .env file for correct SMTP settings.
  2. Review Laravel logs at storage/logs/laravel.log for errors.
  3. If using queues, make sure your queue worker is running:
php artisan queue:work
  1. For production, use a reliable SMTP service like SendLayer to ensure deliverability.

Routes Not Working (404 Error)

If you’re getting 404 errors when accessing password reset routes:

  1. Clear your route cache:
php artisan route:clear
php artisan cache:clear
  1. Verify routes are registered:
php artisan route:list | grep password
  1. Make sure you’ve added the routes to the correct file (web.php or api.php)

Best Practices for Production

When deploying a password reset system to production, follow these security best practices:

Use HTTPS

Always use HTTPs in production. Password reset links should never be sent over unsecured connections.

In your .env:

APP_URL=https://yourdomain.com

Queue Email Sending

For better performance, queue password reset emails instead of sending them synchronously:

public function sendPasswordResetNotification($token)
{
    $this->notify((new CustomResetPasswordNotification($token))->delay(now()->addSeconds(5)));
}

Make sure to run your queue worker:

php artisan queue:work

Implement Rate Limiting

Prevent abuse by limiting password reset requests. Laravel includes throttling by default, but you can customize it:

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_reset_tokens',
        'expire' => 60,
        'throttle' => 60, // Only allow 1 request per 60 seconds
    ],
],

Don’t Reveal User Existence

For security reasons, don’t tell attackers whether an email exists in your system. Always return the same message:

public function sendResetLink(Request $request)
{
    $request->validate(['email' => 'required|email']);
    
    Password::sendResetLink($request->only('email'));
    
    // Always return success message
    return back()->with('status', 'If this email exists, a password reset link has been sent.');
}

FAQs – Laravel Password Reset

These are answers to some of the most common questions we see about implementing password resets in Laravel.

How do I customize the Laravel reset password email template?

To customize the reset email, you’ll need to create a custom notification class and override the sendPasswordResetNotification method in your User model. See the Customize the Email Template section above for more details.

Can I use Laravel password reset without email?

Yes, you can implement OTP-based reset using SMS or store tokens in your database. Then, you can verify them manually before allowing password changes.

Is Laravel’s password reset secure for production?

Yes, Laravel uses industry-standard security practices including token hashing, expiration, and secure random generation. Just ensure you’re using HTTPS and following best practices.

How do I implement password reset for multiple user types?

Create separate password brokers in config/auth.php for each user type (users, admins, etc.) and use different guards.

That’s It! Now You Know How to Implement Password Reset in Laravel.

Next, would you like to ensure your password reset emails follow industry standards? Check out our tutorial on best practices for password reset emails.

  • 200 Free Emails
  • Easy Setup
  • 5 Star Support

Ready to send your emails in the fastest and most reliable way? Get started today with the most user-friendly and powerful SMTP email delivery service. SendLayer Business includes 5,000 emails a month with premium support.