- vừa được xem lúc

Laravel Forgot Password : Gửi mail chứa OTP và sử dụng ajax

0 0 5

Người đăng: Đương Nguyễn Đình

Theo Viblo Asia

Hi, xin chào mọi người

Đây là cách làm của mình, mọi người có thể tham khảo nha.

Setting .env

Setting .env và bật bảo mật 2 lớp cho gmail và tạo mật khẩu ứng dụng cho gmail, mật khẩu ứng dụng 12 ký tự này chính là password_email

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=address_email
MAIL_PASSWORD=password_email
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME=

Thêm 2 trường confirm_otp, token_forgot

Ở đây mình viết thêm file migration để add thêm 2 column mới cho table users, sau đó chạy php artisan migrate

<?php use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; return new class extends Migration
{ /** * Run the migrations. */ public function up(): void { Schema::table('users', function($table) { $table->string('confirm_otp')->default(NULL); $table->string('token_forgot')->default(NULL); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('users', function($table) { $table->dropColumn('confirm_otp'); $table->dropColumn('token_forgot'); }); }
}; 

Thêm route

Route::get('/forgot-password', [ForgotPasswordController::class, 'index'])->name('forgot-password');
Route::post('/forgot-password', [ForgotPasswordController::class, 'forgot_password'])->name('forgot-password');
Route::get('/forgot_otp/{token}', [ForgotPasswordController::class, 'forgot_otp_view'])->name('forgot_otp_view');
Route::post('/forgot_otp', [ForgotPasswordController::class, 'forgot_otp'])->name('forgot_otp');
Route::post('/re_forgot', [ForgotPasswordController::class, 're_forgot'])->name('re_forgot');

Mọi người tự thiết kế 2 màn:

Màn 1: Để nhập email
Đoạn call ajax khi nhập email, mọi người có thể tham khảo

$('#submit-forgot').click(function(e) { e.preventDefault(); var formData = new FormData($('#formAuthentication')[0]); $.ajax({ type: "POST", url: "{{ route('forgot-password') }}", data: formData, cache: false, contentType: false, processData: false, success: function(data) { let path = document.querySelector('html').getAttribute('data-path') window.location.href = path + 'forgot_otp/' + data.token; }, error: function(a) { $('.message-error').remove(); let error = a['responseJSON'].message; if (error) { $('#email').parent().append('<div class="alert alert-danger message-error" role="alert">' + error + '</div>'); } else { $('#email').parent().append('<div class="alert alert-danger message-error" role="alert">' + a['responseJSON'].email + '</div>'); } } }); });

Màn 2: Để nhập OTP
Đoạn call ajax nhập OTP
 $('#submit-otp').click(function(e) { e.preventDefault(); var formData = new FormData($('#twoStepsForm')[0]); $.ajax({ type: "POST", url: "{{ route('forgot_otp') }}", data: formData, cache: false, contentType: false, processData: false, success: function(data) { toastr.success(data.message); let path = document.querySelector('html').getAttribute('data-path'); setTimeout(() => { window.location.href = path + 'login'; // đoạn này mình cho sau 2 giây mới redirect để user còn nhìn thấy alert success }, 2000); }, error: function(a) { let error = a['responseJSON'].message; $('.message-error').remove(); $('#twoStepsForm .fv-plugins-icon-container').addClass( 'fv-plugins-bootstrap5-row-invalid'); if (error) { $('#otp').parent().append( '<div class="fv-plugins-message-container invalid-feedback message-error"><div data-field="otp" data-validator="notEmpty">' + error + '</div></div>'); toastr.error(error); } else { $('#otp').parent().append( '<div class="fv-plugins-message-container invalid-feedback message-error"><div data-field="otp" data-validator="notEmpty">' + a['responseJSON'].otp + '</div></div>'); // show message, mn có thể tùy chỉnh toastr.error(a['responseJSON'].otp); // Show alert error } } }); }); $('#re-forgot').click(function(e) { e.preventDefault(); var formData = new FormData(); formData.append('token', "{{ $token }}"); formData.append('_token', "{{ csrf_token() }}"); $.ajax({ type: "POST", url: '/re_forgot', data: formData, cache: false, contentType: false, processData: false, success: function(data) { toastr.success("OTP has been sent."); // ở đây mình dùng toastr để show alert message }, error: function(a) { // toastr.error(a['responseJSON'].message); } }); });

Gửi mail

Tạo 2 mẫu cho mail, 1 cho gửi OTP, 1 cho gửi newpassword

php artisan make:mail UserForGot

php artisan make:mail UserNewPassword

<?php
namespace App\Mail; use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; class UserForGot extends Mailable
{ use Queueable, SerializesModels; /** * Create a new message instance. */ protected $user; public function __construct(User $user) { $this->user = $user; } public function build() { return $this->view('mail.verify_forgot')->with(['user' => $this->user, 'token' => $this->user->token_forgot]); }
}
<?php
namespace App\Mail; use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels; class UserNewPassword extends Mailable
{ use Queueable, SerializesModels; /** * Create a new message instance. */ protected $user; public function __construct(User $user) { $this->user = $user; } public function build() { return $this->view('mail.new_password')->with(['user' => $this->user, 'password' => $this->user->password_new]); }
} 

Tạo 2 template gửi mail

Chỗ này mn tùy chỉnh miễn sao show ra được otp ra: $user->confirm_otp

  • Màn content email verify_forgot.blade.php
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8" /> <title></title> <style> body { margin: 0; padding: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #333; background-color: #fff; } .container { margin: 0 auto; width: 100%; max-width: 600px; padding: 0 0px; padding-bottom: 10px; border-radius: 5px; line-height: 1.8; } .header { border-bottom: 1px solid #eee; } .header a { font-size: 1.4em; color: #000; text-decoration: none; font-weight: 600; } .content { min-width: 700px; overflow: auto; line-height: 2; } .otp { background: linear-gradient(to right, #00bc69 0, #00bc88 50%, #00bca8 100%); margin: 0 auto; width: max-content; padding: 0 10px; color: black; border-radius: 4px; text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; } .footer { color: #aaa; font-size: 0.8em; line-height: 1; font-weight: 300; } .email-info { color: #666666; font-weight: 400; font-size: 13px; line-height: 18px; padding-bottom: 6px; } .email-info a { text-decoration: none; color: #00bc69; } </style>
</head> <body> <div class="container"> <div class="header"> <a>Prove Your [company name] Identity</a> </div> <br /> <strong>Dear {{ $user->last_name }},</strong> <p> We have received your [company name] account registration request. <br /> <b>Your OTP code is:</b> </p> <h2 class="otp">{{ $user->confirm_otp }}</h2> <h5>Click on the link below to enter OTP:</h5> <a href="{{ route('forgot_otp_view', $token) }}">{{ route('forgot_otp_view', $token) }}</a> <p style="font-size: 0.9em"> <br /> <br /> Please ensure the confidentiality of your OTP and do not share it with anyone.<br /> <strong>Do not forward or give this code to anyone.</strong> <br /> <br /> <strong>Thank you for using [company name].</strong> <br /> <br /> Best regards, <br /> <strong>[company name]</strong> </p> <hr style="border: none; border-top: 0.5px solid #131111" /> <div class="footer"> <p>This email can't receive replies.</p> <p> For more information about [app name] and your account, visit <strong><a href="{{ route('login') }}" alt="companyABC">[app name]</a></strong> // Công ty ABC </p> </div> </div> <div style="text-align: center"> <div class="email-info"> &copy; 2024 [company]. All rights reserved. </div> </div>
</body>
</html> 
  • Màn content email new_password.blade.php
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8" /> <title></title> <style> body { margin: 0; padding: 0; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #333; background-color: #fff; } .container { margin: 0 auto; width: 100%; max-width: 600px; padding: 0 0px; padding-bottom: 10px; border-radius: 5px; line-height: 1.8; } .header { border-bottom: 1px solid #eee; } .header a { font-size: 1.4em; color: #000; text-decoration: none; font-weight: 600; } .content { min-width: 700px; overflow: auto; line-height: 2; } .otp { background: linear-gradient(to right, #00bc69 0, #00bc88 50%, #00bca8 100%); margin: 0 auto; width: max-content; padding: 0 10px; color: black; border-radius: 4px; text-shadow: 2px 0 #fff, -2px 0 #fff, 0 2px #fff, 0 -2px #fff, 1px 1px #fff, -1px -1px #fff, 1px -1px #fff, -1px 1px #fff; } .footer { color: #aaa; font-size: 0.8em; line-height: 1; font-weight: 300; } .email-info { color: #666666; font-weight: 400; font-size: 13px; line-height: 18px; padding-bottom: 6px; } .email-info a { text-decoration: none; color: #00bc69; } </style>
</head> <body> <div class="container"> <div class="header"> <a>YOUR NEW PASSWORD</a> </div> <br /> <strong>Dear {{ $user->last_name }},</strong> <p> OTP confirmation successful. <br /> <b>Your Password is:</b> </p> <h2 class="otp">{{ $password }}</h2> <p style="font-size: 0.9em"> <br /> <br /> <a href="{{ route('login') }}" alt="[app name]">LOGIN NOW</a> <br /> <br /> <strong>Thank you for using [app name].</strong> <br /> <br /> Best regards, <br /> <strong>[company name]</strong> </p> <hr style="border: none; border-top: 0.5px solid #131111" /> <div class="footer"> <p>This email can't receive replies.</p> <p> For more information about [app name] and your account, visit <strong><a href="{{ route('login') }}" alt="[company name]">[App name]</a></strong> </p> </div> </div> <div style="text-align: center"> <div class="email-info"> &copy; 2024 [company name]. All rights reserved. </div> </div>
</body>
</html> 

ForgotPasswordController

Trong đây mình có thêm điều kiện với những accoutn chưa verify khi đăng kí (kiểu như chưa nhập OTP nên mình có check thêm tại đây, mn có thể bỏ mấy đoạn logic ko cần thiết đi cho phù hợp nha)

 <?php namespace App\Http\Controllers\Client\Auth; use App\Http\Controllers\Controller;
use App\Mail\UserForGot;
use App\Mail\UserNewPassword;
use App\Mail\UserVerification;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str; class ForgotPasswordController extends Controller
{ public function index() { return view('auth.client.forgot-password'); } public function forgot_password(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:100', ]); if ($validator->fails()) { return response()->json($validator->errors(), 400); } $user = User::where('email', $request->email)->first(); if (!$user || $user->is_verify != User::VERIFY) { return response()->json(['message' => 'Account not registered!'], 400); } try { DB::beginTransaction(); $user->confirm_otp = rand(100000, 999999); // $user->confirmation_code_expired_in = Carbon::now()->addSecond(60); $token = Str::random(64); $user->token_forgot = $token; $user->save(); Mail::to($user->email)->send(new UserForGot($user)); DB::commit(); return response()->json(['token' => $token], 200); } catch (\Throwable $exeption) { return $this->sendError(null, 400, $exeption->getMessage()); DB::rollBack(); } // return redirect()->route('forgot_otp_view', $token); } public function forgot_otp_view($token) { return view('auth.client.forgot-otp', compact('token')); } public function forgot_otp(Request $request) { $validator = Validator::make($request->all(), [ 'token' => 'required|string', 'otp' => 'required|string', ]); if ($validator->fails()) { return response()->json($validator->errors(), 400); } $users = User::where('is_verify', User::VERIFY)->get(); try { DB::beginTransaction(); foreach ($users as $user) { if ($user->token_forgot == $request->token) { if ($user->confirm_otp == $request->otp) { $password = Str::random(8); $user->password = Hash::make($password); $user->token_forgot = ''; $user->save(); $user->password_new = $password; Mail::to($user->email)->send(new UserNewPassword($user)); break; } else { return response()->json(['status' => false, 'message' => 'Your OTP is invalid'], 400); } } } DB::commit(); return response()->json(['status' => true, 'message' => 'Password has been sent to your inbox.'], 200); } catch (\Throwable $exeption) { DB::rollBack(); return $this->sendError(null, 400, $exeption->getMessage()); } } public function re_forgot(Request $request) { $validator = Validator::make($request->all(), [ 'token' => 'required|string|max:100', ]); if ($validator->fails()) { return response()->json($validator->errors(), 400); } // $user = User::where('email', $request->email)->first(); $users = User::where('is_verify', User::VERIFY)->get(); try { DB::beginTransaction(); foreach ($users as $user) { if (Hash::check($user->email, $request->token)) { if ($user['is_verify'] == User::VERIFY) { $user->confirm_otp = rand(100000, 999999); // $user->confirmation_code_expired_in = Carbon::now()->addSecond(60); $user->token_forgot = $request->token; $user->save(); Mail::to($user->email)->send(new UserForGot($user)); break; } else { return response()->json(['status' => false, 'message' => 'Account not registered!'], 400); } } } DB::commit(); return response()->json(['status' => true, 'message' => 'OTP has been sent to your inbox.'], 200); } catch (\Throwable $exeption) { DB::rollBack(); return $this->sendError(null, 400, $exeption->getMessage()); } }
} 

Bổ sung 1 chút, trong class Controller mình có thêm 2 function này
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; class Controller extends BaseController
{ use AuthorizesRequests, ValidatesRequests; public function sendSuccess($data, $code = 200, $message = '') { return response()->json([ 'data' => $data, 'success' => true, 'message' => $message, ], $code); } public function sendError($data = null, $code = 400, $message = '') { return response()->json([ 'data' => $data, 'success' => false, 'message' => $message, ], $code); }
} 

Cảm ơn mn đã dành thời gian đọc hết bài. Chúc mọi người thành công

Bình luận

Bài viết tương tự

- vừa được xem lúc

Upload file ajax với FormData

1. FormData là gì. FormData là một interface mới được HTML5 giới thiệu trong Web API. 2.

0 0 36

- vừa được xem lúc

Một số ví dụ sử dụng Ajax

1. Định nghĩa về ajax. AJAX viết tắt từ Asynchronous JavaScript and XML (JavaScript và XML không đồng bộ). .

0 0 54

- vừa được xem lúc

Tìm hiểu về FormData

1. Bài toán đặt ra. bạn sẽ thường phải chặn việc submit form và sử dụng ajax để xử lý dữ liệu form gửi lên. Bài toán 1:.

0 0 36

- vừa được xem lúc

Cùng xây dựng app Ajax đơn giản trong Rails

Chúng ta cùng thử viết một Web app đơn giản có sử dụng Ajax bằng Ruby on Rails nhé! . Trong bài viết mình sẽ nói thẳng vào cách xây dựng ajax luôn nên nếu các bạn chưa nắm được cách viết 1 app CRUD bằ

0 0 38

- vừa được xem lúc

Tính năng Giỏ Hàng trong Rails

Ở bài viết này mình sẽ tóm tắt cách dùng Ruby on Rails và Ajax để tạo 1 app có chức năng "thêm vào giỏ hàng" như các shop online. ! .

0 0 48

- vừa được xem lúc

[GITHUB] 2FA của github hoạt động như thế nào? | Mất 2FA thì lên github mà hỏi | TOTP

Two Factor Authentication của github dùng phương pháp gì. Xác thực hai yếu tố (2 lớp) là gì. . Sau khi bật xác thực 2 lớp:.

0 0 44