Helo teman-teman, kali ini saya akan membahas bagaimana cara membuat kode OTP di PHP atau lebih tepatnya dengan menggunakan framework Laravel.
Apa itu kode OTP?
Kode OTP adalah singkatan dari kode One Time-Password atau bisa juga diartikan sebagai kode password yang hanya bersifat sementara dan dapat digunakan satu kali saja. Karena kode OTP ini hanya bisa dipakai 1 kali biasanya kode OTP ini memiliki waktu expired-nya.
Daftar Isi
Kode One Time-Password umumnya digunakan sebagai password sekali pakai yang ditujukan untuk melakukan proses verifikasi, authentikasi. Bisa juga digunakan untuk meningkatkan sekuriti dari sistem yang dibuat sebagai lapis ke dua selain menggunakan password umum.
Kode OTP ini biasanya dikirim via SMS, Online Chatting App seperti WA/Telegram atau ke alamat email.
Oh iya, kalau mau nonton versi video, bisa tonton di youtube saya ya, ingat juga di subscribe :D
Flow security dengan OTP
Ada banyak flow implementasi OTP, tapi kali ini saya akan coba membuat flow yang sangat simple agar teman-teman memahami konsep OTP dengan lebih baik.
Flownya adalah:
- User daftar dan mengisi data user yang dibutuhkan
- Setelah user berhasil mendaftar, redirect ke halaman validasi OTP
- Kirim OTP ke email user
- user mengisi kode OTP
- Sistem memvalidasi kode OTP
- aktivasi user dan dan loginkan user.
Nah flownya sangat simple kan? Jadi selain bisa digunakan untuk verifikasi email user yang digunakan apakah aktif atau tidak, kode OTP ini juga nanti bisa dikembangkan lagi dengan membuat security lapis ke dua saat login.
Instalasi Laravel
Kita sudah belajar flownya, saatnya instalasi laravel dulu.
Mungkin temen-teman sudah punya aplikasi Laravel yang sedang berjalan bisa pakai aplikasi yang sudah dibangun ya. Nah bagi yang belum bisa coba install laravel dulu.
Saya akan install secara cepat saja langsung dengan command composer composer create-project laravel/laravel otp-sample
Setelah instalasi selesai, pergi ke folder otp-sample
atau folder lain sesuai yang teman-teman buat.
Install Package Brezze
Kita akan pakai package breeze dari Laravel langsung untuk mengurus soal authentication. Jadi kita tidak buat dari 0 lagi. Cek lebih detail di: https://github.com/laravel/breeze
Kita akan sedikit memodifikasi untuk implementasi OTP kode saat register.
Untuk install breeze, jalankan command berikut.
composer require laravel/breeze --dev
Setelah selesai di install, jalankan command selanjutnya.
php artisan breeze:install
Dalam tutorial kali ini, saya akan pakai blade template saja, kalian bisa coba menggunakan react/vue sesuai dengan frontend yang diinginkan.
Nah setelah laravel dan brezze sudah kita install, maka selanjutnya coba jalankan php artisan serve untuk melilhat laravel project di browser kita.
Buka http://localhost:8000
Update .ENV dan Migrate
Hampir kelupaan, jadi karena kita akan kirim email, saya akan setting MAIL_MAILER=log
untuk testing email nanti.
Jangan lupa juga setup databasenya disesuaikan dengan database kalian ya.
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:SgQ2Gp+CTCRMluws6oU9Zxr3OquUZAzHeAQbBBTx8hg=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=otp_example
DB_USERNAME=root
DB_PASSWORD=
DB_SOCKET='/Applications/MAMP/tmp/mysql/mysql.sock'
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
Jangan lupa ulangin php artisan serve
jika untuk me refresh settingnya.
Buat table dan model untuk OTP User
Untuk membuat table otp, kita akan buat dulu sebuah migration dengan command seperti berikut ini:
php artisan make:migration create_user_otps
Setelah selesai dibuat, buka file migration tersebut dan sesuaikan isi filenya seperti berikut ini:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_otps', function (Blueprint $table) {
$table->id();
$table->integer('user_id');
$table->integer('otp_code');
$table->dateTime('expired_at');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_otps');
}
};
Kita akan membuat table user_otps
yang akan menampung data OTP dengan fieldnya yaitu:
- id
- user_id : untuk relasi ke table user
- otp_code: kode otp, dimana angka yang panjangnya 6 huruf.
- expired_at: datetime, agar kode OTP tidak bisa dipakai lagi jika sudah lewat expired datenya.
Jika sudah, run migration dengan menjalankan command: php artisan migrate
Selanjutnya adalah membuat model UserOTP dengan command php artisan make:model UserOTP
Buka file UserOTP.php
dan sesuaikan isinya seperti berikut:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class UserOTP extends Model
{
protected $table = 'user_otps';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'otp_code',
'user_id',
'expired_at'
];
public function user(){
return $this->belongsTo(User::class,'user_id');
}
}
Langkah selanjutnya adalah menambahkan relasi activeOTP di model user.
Buka file model User.php dan tambahkan relasi berikut:
public function activeOTP()
{
return $this->hasOne(UserOTP::class,'user_id')->where('expired_at','>', 'now()');
}
Nah model dan databasenya sudah siap, saatnya membuat alur untuk OTP nya.
Modifikasi Register Controller
Secara default, saat submit register, file yang digunakan adalah RegisteredUserController.php
khususnya di method store()
Sekarang buka file RegisteredUserController.php
, kita akan melakukan sedikit perubahan di dalam method store()
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
UserOTP::create([
'user_id' => $user->id,
'otp_code' => rand(100000,999999),
'expired_at' => Date::now()->addMinutes(5)
]);
event(new Registered($user));
return redirect()->route('otp-verification', $user->id);
}
Jadi saat register, kita akan membuat kode OTP yang akan dikirim ke email user.
Kita perlu merubah alur verifikasi email yang dikirim melalui event Registered()
.
Buat Listener dan Notification file Untuk Kirim OTP
Kita akan buat 2 file untuk listener dan notification mengirim kode OTP ke email user.
Jalankan 2 command ini untuk membuat 2 file tersebut.
php artisan make:listener SendOTPEventListener
php artisan make:notification SendOTPNotification
Buka file SendOTPEventNotification.php
dan update isinya seperti dibawah ini:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SendOTPNotification extends Notification
{
use Queueable;
public $otp;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($otp)
{
$this->otp = $otp;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('Your OTP code: '.$this->otp)
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
File ini akan bertugas untuk mengirimkan email ke user yang berisi kode OTP nantinya.
Buka file SendOTPEventListener.php
dan update isinya seperti dibawah ini:
<?php
namespace App\Listeners;
use App\Notifications\SendOTPNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendOTPEventListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$event->user->notify(new SendOTPNotification($event->user->activeOTP->otp_code));
}
}
Langkah selanjutnya adalah kita perlu merubah listener saat Registered event dipanggil.
Buka file EventServiceProvider.php
dan edit dibagian listen-nya menjadi seperti dibawah ini:
<?php
namespace App\Providers;
use App\Listeners\SendOTPEventListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
// SendEmailVerificationNotification::class,
SendOTPEventListener::class
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
//
}
/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return false;
}
}
Jadi ceritanya nanti saat ada event Registered
, app akan memanggil listener SendOTPEventListener
untuk mengirim email notifikasi ke user.
Desain seperti ini biasanya disebut sebagai event driven pattern.
Buat tampilan halaman OTP
Masih ingat dengan flow sebelumnya?
Jadi saat selesai mengisi data registrasi, kita akan redirect ke halaman untuk mengisi kode OTP.
Buat sebuah route dan view baru untuk tampilan form validasi kode OTP.
Buka file web.php
dan tambahkan route ini:
// Route lainnya
use App\Models\User;
Route::get('/{user_id}/otp-verification', function ($user_id) {
$user = User::find($user_id);
return view('otp-verification', compact('user'));
})->middleware([])->name('otp-verification');
Selanjutnya buat sebuah view baru dengan nama otp-verification.blade.php
<x-guest-layout>
<form method="POST" action="{{ route('otp.validation', $user->id) }}">
@csrf
<div>
<x-input-label for="otp" :value="__('OTP CODE')" />
<x-text-input id="otp" class="block mt-1 w-full" type="text" name="otp_code" required autofocus />
<x-input-error :messages="$errors->get('otp_code')" class="mt-2" />
</div>
<div class="flex items-center justify-center mt-4">
<x-primary-button>
{{ __('Validate OTP CODE') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
Jangan lupa buat 1 buah route baru lagi untuk menangani post data dan mengecek apakah kode OTP tersebut valid atau tidak.
Buka lagi file web.php
dan tambahkan route baru ini:
Route::post('/{user_id}/otp-validation', function ($user_id, Request $request) {
$otp = UserOTP::where('otp_code', $request->otp_code)->where('expired_at','>',now())->first();
if (!$otp) {
return redirect()->back()->withErrors([
'otp_code' => 'OTP CODE tidak ditemukan.'
]);
}
$otp->user->email_verified_at = Date::now();
$otp->user->save();
Auth::login($otp->user);
return redirect(RouteServiceProvider::HOME);
})->middleware([])->name('otp.validation');
Okay, semua proses untuk validasi sudah selesai
Tampilannya kurang lebih seperti berikut ini.
Sekarang saatnya mencobanya ya.
Silakan daftar dan jika berhasil akan dialihkan ke halaman kode OTP.
Kode OTP bisa kalian cek di laravel.log untuk isi emailnya.
Jika sudah berhasil mengisi kode OTP, maka user akan otomatis terverifikasi dan langsung login ke dalam sistem.
Bagaimana cara validasi OTP?
OTP yang kita buat ini sangat simple. Konsepnya adalah mengganti verifikasi via link dengan kode OTP.
Jadi saat verifikasi kode OTP, jika kode OTP benar, maka kita update field email_verified_at
ke data saat itu juga.
Dengan begitu, artinya user berhasil memverifikasi datanya dengan benar.
Kesimpulan
Jadi itulah cara membuat implementasi kode OTP di laravel dengan sederhana. Sebenarnya ada beberapa use case yang bisa dikembangkan lagi, misalnya bagaimana menghandle saat token OTPnya expired. Bisa juga dikembangkan menjadi tambahan security untuk login user, dimana selain login dengan password, perlu mengisi kode OTP juga saat login.
Mungin nanti akan saya tambahkan beberapa use case lainnya di artikel lanjutkan mengenai cara membuat kode OTP dengan laravel.
Semoga tutorial ini membantu ya.
Update:
Karena ada yang request update artikel, bagaimana caranya agar kode otp ini dikirimkan ke WA? Kita akan update lagi artikelnya dengan menambahkan notif OTP Laravel ke WA. Kita akan menggunakan bantuan pihak ketiga yaitu fonnte, jadi kalian bisa daftar dulu disini: Register Fonnte