Forgot Password Vue Laravel

User
forget password feature vue js with laravel backend
Assistant
To implement a **Forgot Password** feature in a Vue.js frontend with a Laravel backend, follow these steps:

## **Laravel Backend: Setting Up Password Reset**
Laravel provides built-in support for password resets.

### **Step 1: Configure Auth for API**
Ensure your Laravel authentication system supports password resets via API.

#### **1. Setup Laravel Breeze (if not already done)**
Since you're using Laravel with Vue and Inertia.js, Breeze is a good choice:

composer require laravel/breeze --dev
php artisan breeze:install api
php artisan migrate

#### **2. Create Password Reset Routes**
Modify `routes/api.php` to include password reset endpoints:

use AppHttpControllersAuthPasswordResetController;
use IlluminateSupportFacadesRoute;

Route::post('/forgot-password', [PasswordResetController::class, 'sendResetLink']);
Route::post('/reset-password', [PasswordResetController::class, 'reset']);

#### **3. Create the Password Reset Controller**
Run the command:

php artisan make:controller Auth/PasswordResetController

Then, in `app/Http/Controllers/Auth/PasswordResetController.php`, add:

namespace AppHttpControllersAuth;

use IlluminateHttpRequest;
use AppHttpControllersController;
use IlluminateSupportFacadesPassword;
use IlluminateValidationValidationException;
use IlluminateAuthEventsPasswordReset;
use IlluminateSupportFacadesHash;
use IlluminateSupportStr;
use AppModelsUser;

class PasswordResetController extends Controller
{
    // Send Reset Link
    public function sendResetLink(Request $request)
    {
        $request->validate(['email' => 'required|email']);

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

        return response()->json(['message' => __($status)], $status === Password::RESET_LINK_SENT ? 200 : 400);
    }

    // Reset Password
    public function reset(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->forceFill([
                    'password' => Hash::make($password),
                    'remember_token' => Str::random(60),
                ])->save();

                event(new PasswordReset($user));
            }
        );

        return response()->json(['message' => __($status)], $status === Password::PASSWORD_RESET ? 200 : 400);
    }
}

## **Vue.js Frontend: Implementing Forgot Password**
You'll create two pages:
1. **Forgot Password Page** โ€“ User enters email to receive reset link.
2. **Reset Password Page** โ€“ User sets a new password using the token.

### **1. Forgot Password Page (Email Submission)**
Create `ForgotPassword.vue` in `resources/js/Pages/Auth/`:

<script setup>
import { ref } from "vue";
import axios from "axios";

const email = ref("");
const message = ref("");
const error = ref("");

const sendResetLink = async () => {
  error.value = "";
  message.value = "";

  try {
    const response = await axios.post("/api/forgot-password", { email: email.value });
    message.value = response.data.message;
  } catch (err) {
    error.value = err.response?.data?.message || "Something went wrong!";
  }
};
</script>

<template>
  <div class="max-w-md mx-auto p-6 bg-white shadow-md rounded">
    <h2 class="text-xl font-semibold mb-4">Forgot Password</h2>

    <div v-if="message" class="text-green-500">{{ message }}</div>
    <div v-if="error" class="text-red-500">{{ error }}</div>

    <input
      v-model="email"
      type="email"
      placeholder="Enter your email"
      class="w-full p-2 border rounded mb-4"
    />

    <button @click="sendResetLink" class="w-full bg-blue-500 text-white p-2 rounded">
      Send Reset Link
    </button>
  </div>
</template>

### **2. Reset Password Page (New Password Form)**
Create `ResetPassword.vue` in `resources/js/Pages/Auth/`:

<script setup>
import { ref } from "vue";
import axios from "axios";
import { useRoute, useRouter } from "vue-router";

const route = useRoute();
const router = useRouter();

const email = ref(route.query.email || "");
const token = ref(route.query.token || "");
const password = ref("");
const password_confirmation = ref("");
const message = ref("");
const error = ref("");

const resetPassword = async () => {
  error.value = "";
  message.value = "";

  try {
    const response = await axios.post("/api/reset-password", {
      email: email.value,
      token: token.value,
      password: password.value,
      password_confirmation: password_confirmation.value,
    });

    message.value = response.data.message;
    setTimeout(() => router.push("/login"), 3000); // Redirect to login after success
  } catch (err) {
    error.value = err.response?.data?.message || "Something went wrong!";
  }
};
</script>

<template>
  <div class="max-w-md mx-auto p-6 bg-white shadow-md rounded">
    <h2 class="text-xl font-semibold mb-4">Reset Password</h2>

    <div v-if="message" class="text-green-500">{{ message }}</div>
    <div v-if="error" class="text-red-500">{{ error }}</div>

    <input v-model="email" type="email" placeholder="Email" class="w-full p-2 border rounded mb-4" disabled />

    <input v-model="password" type="password" placeholder="New Password" class="w-full p-2 border rounded mb-4" />
    
    <input v-model="password_confirmation" type="password" placeholder="Confirm Password" class="w-full p-2 border rounded mb-4" />

    <button @click="resetPassword" class="w-full bg-blue-500 text-white p-2 rounded">
      Reset Password
    </button>
  </div>
</template>

## **3. Adding Routes in Vue Router**
Ensure you have these routes in `resources/js/router/index.js`:

import { createRouter, createWebHistory } from "vue-router";
import ForgotPassword from "../Pages/Auth/ForgotPassword.vue";
import ResetPassword from "../Pages/Auth/ResetPassword.vue";

const routes = [
  { path: "/forgot-password", component: ForgotPassword },
  { path: "/reset-password", component: ResetPassword },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

## **4. Configuring Mail in Laravel**
To send password reset emails, configure your `.env` file:

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-email-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=your-email@gmail.com
MAIL_FROM_NAME="Your App Name"

Then, clear the cache:

php artisan config:clear

## **Conclusion**
Now you have a fully functional **Forgot Password** feature using Laravel for the backend and Vue.js for the frontend. ๐ŸŽ‰

Let me know if you need any modifications! ๐Ÿš€

User
Assistant
To dynamically change the view in your `OtpMail` class, you can pass the view name as a parameter in the constructor. Here's how you can modify your `OtpMail` class:

### **Updated `OtpMail.php`**

namespace AppMail;

use IlluminateBusQueueable;
use IlluminateContractsQueueShouldQueue;
use IlluminateMailMailable;
use IlluminateMailMailablesContent;
use IlluminateMailMailablesEnvelope;
use IlluminateQueueSerializesModels;

class OtpMail extends Mailable
{
    use Queueable, SerializesModels;

    public $otp;
    public $viewName;

    /**
     * Create a new message instance.
     */
    public function __construct($otp, $viewName = 'emails.otp')
    {
        $this->otp = $otp;
        $this->viewName = $viewName; // Store the dynamic view name
    }

    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Your OTP Code',
        );
    }

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: $this->viewName, // Use the dynamic view name
            with: [
                'otp' => $this->otp,
            ]
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array<int, IlluminateMailMailablesAttachment>
     */
    public function attachments(): array
    {
        return [];
    }
}

### **How to Use It in a Controller**
When sending an email, specify the view dynamically:

use AppMailOtpMail;
use IlluminateSupportFacadesMail;

$otp = rand(100000, 999999);

// Send email with the default view
Mail::to('user@example.com')->send(new OtpMail($otp));

// Send email with a custom view
Mail::to('user@example.com')->send(new OtpMail($otp, 'emails.custom_otp'));

### **How to Create Multiple Views**
You can create different views in `resources/views/emails/`, for example:

#### **Default OTP View (`resources/views/emails/otp.blade.php`)**

<p>Your OTP Code is: <strong>{{ $otp }}</strong></p>

#### **Custom OTP View (`resources/views/emails/custom_otp.blade.php`)**

<p>Hello User,</p>
<p>Here is your OTP: <strong>{{ $otp }}</strong></p>
<p>Thank you for using our service.</p>

### **Conclusion**
Now, you can send emails using different views dynamically by specifying the view name when creating the `OtpMail` instance.

Let me know if you need further customization! ๐Ÿš€

User
custom reset password in laravel
Assistant
### **Custom Reset Password in Laravel**
To implement a custom password reset functionality in Laravel, follow these steps:

## **1. Create the Reset Password Routes**
Modify your `routes/api.php` (or `web.php` if using a web-based approach):

use AppHttpControllersAuthResetPasswordController;

Route::post('/password/forgot', [ResetPasswordController::class, 'sendResetOtp']);
Route::post('/password/reset', [ResetPasswordController::class, 'resetPassword']);

## **2. Create the Reset Password Controller**
Generate a new controller:

php artisan make:controller Auth/ResetPasswordController

Then, modify `app/Http/Controllers/Auth/ResetPasswordController.php`:

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use AppMailOtpMail;
use AppModelsUser;
use IlluminateHttpRequest;
use IlluminateSupportFacadesHash;
use IlluminateSupportFacadesMail;
use IlluminateSupportFacadesValidator;
use CarbonCarbon;

class ResetPasswordController extends Controller
{
    /**
     * Send OTP for password reset.
     */
    public function sendResetOtp(Request $request)
    {
        $request->validate([
            'email' => 'required|email|exists:users,email',
        ]);

        $user = User::where('email', $request->email)->first();
        $otp = rand(100000, 999999);
        
        // Save OTP to the database with an expiry time
        $user->update([
            'reset_otp' => $otp,
            'otp_expires_at' => Carbon::now()->addMinutes(10) // OTP expires in 10 minutes
        ]);

        // Send OTP Email
        Mail::to($user->email)->send(new OtpMail($otp));

        return response()->json(['message' => 'OTP sent to your email.'], 200);
    }

    /**
     * Reset password using OTP.
     */
    public function resetPassword(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|email|exists:users,email',
            'otp' => 'required|digits:6',
            'password' => 'required|string|min:6|confirmed',
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }

        $user = User::where('email', $request->email)->first();

        // Verify OTP and expiry time
        if (!$user || $user->reset_otp !== $request->otp || Carbon::now()->gt($user->otp_expires_at)) {
            return response()->json(['message' => 'Invalid or expired OTP.'], 400);
        }

        // Reset password
        $user->update([
            'password' => Hash::make($request->password),
            'reset_otp' => null, // Clear OTP after use
            'otp_expires_at' => null
        ]);

        return response()->json(['message' => 'Password reset successfully.'], 200);
    }
}

## **3. Modify the User Model**
Add the required columns in the `users` table using a migration:

php artisan make:migration add_reset_otp_to_users_table --table=users

Edit the generated migration file:

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

return new class extends Migration {
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('reset_otp')->nullable();
            $table->timestamp('otp_expires_at')->nullable();
        });
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['reset_otp', 'otp_expires_at']);
        });
    }
};

Run the migration:

php artisan migrate

## **4. Create the OTP Email Template**
Modify `resources/views/emails/otp.blade.php`:

<p>Hello,</p>
<p>Your OTP for password reset is: <strong>{{ $otp }}</strong></p>
<p>This OTP will expire in 10 minutes.</p>
<p>Thank you!</p>

## **5. Implement Vue.js Frontend**
In `ForgotPassword.vue`:

<template>
  <div>
    <h2>Forgot Password</h2>
    <input v-model="email" type="email" placeholder="Enter email" />
    <button @click="sendOtp">Send OTP</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      email: ''
    };
  },
  methods: {
    async sendOtp() {
      try {
        const response = await axios.post('/api/password/forgot', { email: this.email });
        alert(response.data.message);
      } catch (error) {
        alert(error.response.data.message || 'Error sending OTP');
      }
    }
  }
};
</script>

In `ResetPassword.vue`:

<template>
  <div>
    <h2>Reset Password</h2>
    <input v-model="email" type="email" placeholder="Enter email" />
    <input v-model="otp" type="text" placeholder="Enter OTP" />
    <input v-model="password" type="password" placeholder="New Password" />
    <input v-model="password_confirmation" type="password" placeholder="Confirm Password" />
    <button @click="resetPassword">Reset Password</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      email: '',
      otp: '',
      password: '',
      password_confirmation: ''
    };
  },
  methods: {
    async resetPassword() {
      try {
        const response = await axios.post('/api/password/reset', {
          email: this.email,
          otp: this.otp,
          password: this.password,
          password_confirmation: this.password_confirmation
        });
        alert(response.data.message);
      } catch (error) {
        alert(error.response.data.message || 'Error resetting password');
      }
    }
  }
};
</script>

## **Conclusion**
– **Step 1:** Users request an OTP for password reset.
– **Step 2:** Laravel generates and emails an OTP.
– **Step 3:** Users submit the OTP with a new password.
– **Step 4:** Laravel validates and updates the password.

This approach avoids using Laravel's built-in password reset and provides a more customizable OTP-based reset system.

Would you like additional enhancements like rate limiting OTP requests? ๐Ÿš€

User
<script>
import { ref,shallowRef,inject,onMounted } from "vue";
import { useForm, useField } from 'vee-validate'
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import publicApi from '@/publicApi';
import SingleColumn from '@/layouts/SingleColumn.vue';
const authStore = useAuthStore();
const router = useRouter();
const apiUrl = inject('apiUrl');
const showPassword = ref(false);
const alertMessage=ref("");
const alertColor=ref("");
console.log(apiUrl);
const step = ref(1);
const items = [
    'Forget Password',
    'Reset Password',
  ]
  const loading = ref(false);
const otp = shallowRef('')
const { handleSubmit, handleReset, values } = useForm({
    validationSchema: {
      email (value:string) {
        if (/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/i.test(value)) return true

        return 'Must be a valid e-mail.'
      },

    },
  })
  const { handleVerifyOtp} = useForm({
    validationSchema: {
      password (value:string) {
        if (value.length >= 6) return true

        return 'Must be at least 6 characters'
      },
      confirmPassword (value:string) {
        if (value === password.value.value) return true

        return 'Passwords do not match'
      },

    },
  })

 
  const email = useField('email')
  const password = useField('password')
  const confirmPassword = useField('confirmPassword');
  const submit = handleSubmit(async (values) => {
    loading.value = true;
    alertMessage.value = "";
    alertColor.value = "";
    try {
    const response = await publicApi.post(`${apiUrl}/send-otp-password`, {
      email: values.email
    });
    loading.value = false;
   console.log(response);
    if (response.data.success) {
      alertMessage.value = "Otp has been sent successfully!";
      alertColor.value = 'success';
      step.value = 2; // Move to OTP step
    } else {
      console.log(response.data);
      alertMessage.value = response.data.message;
      alertColor.value = 'error';
      console.error("OTP failed:", response.data.message);
    }
  } catch (error:any) {
   console.log(error);
   loading.value = false;
    alertMessage.value =error.response?.data.message;
    alertColor.value = 'error';
    console.error("Error during Forget Password:", error.response?.data.message || error.message);
  }
  })
  // **Verify OTP API Call**
const verifyOtp = handleVerifyOtp(async (values) => {
  loading.value = true;
    alertMessage.value = "";
    alertColor.value = "";
  try {
    const response = await publicApi.post(`${apiUrl}/verify-otp-password`, {
      email: email.value.value,
      password: password.value.value,
      otp: otp.value,
    });

    if (response.data.success) {
      console.log(response.data);
      alertMessage.value = "OTP verified successfully!";
      alertColor.value = 'success';
      authStore.setUser(response.data.user, response.data.token);
      router.push('/dashboard');
      //step.value = 3; // Move to login step
    } else {
      console.log(response.data);
      alertMessage.value = response.data.message || "Invalid OTP.";
      alertColor.value = 'error';
    }
  } catch (error:any) {
    alertMessage.value = error.response?.data?.message || "OTP verification failed.";
    alertColor.value = 'error';
  } finally {
    loading.value = false;
  }
});
onMounted(() => {
  if (authStore.isAuthenticated) {
    router.push("/dashboard"); // Redirect if already logged in
  }
});
</script>

Forget Password

Submit

clear

Verify Your Account

We sent a verification code to {{ email.value.value ? email.value.value : 'test@gmail.com' }}

Please check your email and paste the code below.

Didn't receive the code? Resend

handleVerifyotp does not exist

Assistant
The issue is that you're using `handleVerifyOtp` from `useForm`, but it's not correctly defined in your script. You need to structure it similarly to `handleSubmit`.

### Fix:
Replace this line:

const { handleVerifyOtp} = useForm({

With:

const { handleSubmit: handleVerifyOtp } = useForm({

This correctly renames `handleSubmit` for the OTP verification step.

#### Updated Code:

const { handleSubmit: handleVerifyOtp } = useForm({
    validationSchema: {
      password (value:string) {
        if (value.length >= 6) return true
        return 'Must be at least 6 characters'
      },
      confirmPassword (value:string) {
        if (value === password.value.value) return true
        return 'Passwords do not match'
      },
    },
});

Now, `handleVerifyOtp` will correctly handle the OTP verification. ๐Ÿš€

Leave a Comment

Your email address will not be published. Required fields are marked *