Forgot Password Vue Laravel
March 11, 2025 ยท AI Conversations
—
## **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! ๐
### **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! ๐
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? ๐
<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
### 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. ๐