Cara membuat OTP di Laravel

29 Jul 2022
2145

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.

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.

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:

  1. User daftar dan mengisi data user yang dibutuhkan
  2. Setelah user berhasil mendaftar, redirect ke halaman validasi OTP
  3. Kirim OTP ke email user
  4. user mengisi kode OTP
  5. Sistem memvalidasi kode OTP
  6. aktivasi user dan dan loginkan user.
Flow OTP dengan Laravel

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

Tampilan awal default laravel.

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:

  1. id
  2. user_id : untuk relasi ke table user
  3. otp_code: kode otp, dimana angka yang panjangnya 6 huruf.
  4. 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 

Hasil Migration.

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.

Input kode OTP

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.

Contoh Kode OTP di email

Jika sudah berhasil mengisi kode OTP, maka user akan otomatis terverifikasi dan langsung login ke dalam sistem.

Halaman dashboard

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.

Oh iya, kalau mau nonton versi video, bisa tonton di youtube saya ya, ingat juga di subscribe :D

 

Artikel Lainnya

Artikel lain yang mungkin menarik juga untuk kamu baca.

Quick Test Fitur Baru Laravel 6, Seberapa Efektif Lazzy Collection?

Laravel 6 sudah beberapa minggu ini release. Tentu ada beberapa update fitur-fitur keren yang patut dicoba. Yang paling menarik perhatian saya adalah Lazzy Collection.
Agus Yusida
5 min read 1726

Tutorial Membuat Invoice PDF di Laravel dengan DOMPDF

Halo teman ngide, kali ini kita akan membuat tutorial singkat agaimana cara membuat file PDF pada aplikasi web berbasis Laravel?
Agus Yusida
5 min read 4489

Helllo Laravel! Perkenalan dengan Laravel

Hello ngide, Sekarang mau bahas sebuah framework yang berkembang dengan pesat akhir-akhir ini yaitu Laravel.
Agus Yusida
5 min read 1713

© 2024