Membuat Aplikasi Ekspedisi Lumen 6 #3: Validation & Login

Membuat Aplikasi Ekspedisi Lumen 6 #3: Validation & Login

Pendahuluan

Seri lanjutan dari case membuat Aplikasi ekspedisi menggunakan Lumen 6, dimana pada seri kali ini kita akan belajar membuat validation dan dilengkapi dengan fitur otentikasi dimana dalam hal ini adalah proses login dan reset password.

Tahapan ini akan menjadi penutup dari seri Lumen untuk sementara waktu, karena kita akan berpindah ke seri Nuxt dan Svelte dalam menyediakan UI atau tampilan dari aplikasi yang sedang kita bangun, sekaligus kita juga akan mengintegrasikan antara API yang sudah dibuat di Lumen dan UI yang akan dibuat di Nuxt dan Svelte.

Baca Juga: Membuat Aplikasi Ekspedisi Lumen 6 #2: Authentication & Manage Users

Membuat Validation di Lumen

Pada seri sebelumnya, kita sudah membuat API untuk mengelola data users, akan tetapi pada bagian manipulasi data yang terdiri dari menambahkan dan memperbaharui data belum memiliki validasi untuk memberikan aturan kepada user tentang jenis data yang diinginkan.

Fitur validasi di Lumen sama saja dengan menggunakan validasi di Laravel, adapun penerapannya di dalam case yang kita hadapi adalah buka file UserController.php dan modifikasi method store() dan method update() menjadi.

public function store(Request $request)
{
    //TAMBAHKAN BAGIAN INI
    $this->validate($request, [
        'name' => 'required|string|max:50',
        'identity_id' => 'required|string|unique:users', //UNIQUE BERARTI DATA INI TIDAK BOLEH SAMA DI DALAM TABLE USERS
        'gender' => 'required',
        'address' => 'required|string',
        'photo' => 'nullable|image|mimes:jpg,jpeg,png',
        'email' => 'required|email|unique:users',
        'password' => 'required|min:6',
        'phone_number' => 'required|string',
        'role' => 'required',
        'status' => 'required',
    ]);
    //TAMBAHKAN BAGIAN INI

    //SEDIKIT TYPO DARI VARIABLE $filename, SEHINGGA PERBAHARUI SELURUH VARIABL TERKAIT
    $filename = null;
    if ($request->hasFile('photo')) {
        $filename = Str::random(5) . $request->email . '.jpg';
        $file = $request->file('photo');
        $file->move(base_path('public/images'), $filename); //
    }

    User::create([
        'name' => $request->name,
        'identity_id' => $request->identity_id,
        'gender' => $request->gender,
        'address' => $request->address,
        'photo' => $filename,
        'email' => $request->email,
        'password' => app('hash')->make($request->password),
        'phone_number' => $request->phone_number,
        // 'api_token' => 'test',
        'role' => $request->role,
        'status' => $request->status
    ]);
    return response()->json(['status' => 'success']);
}

public function update(Request $request, $id)
{
    //TAMBAHKAN BAGIAN INI
    $this->validate($request, [
        'name' => 'required|string|max:50',
        'identity_id' => 'required|string|unique:users,identity_id,' . $id, //VALIDASI INI BERARTI ID YANG INGIN DIUPDATE AKAN DIKECUALIKAN UNTUK FILTER DATA UNIK
        'gender' => 'required',
        'address' => 'required|string',
        'photo' => 'nullable|image|mimes:jpg,jpeg,png', //MIMES BERARTI KITA HANYA MENGIZINKAN EXTENSION FILE YANG DISEBUTKAN
        'email' => 'required|email|unique:users,email,' . $id,
        'password' => 'required|min:6',
        'phone_number' => 'required|string',
        'role' => 'required',
        'status' => 'required',
    ]);
    //TAMBAHKAN BAGIAN INI

    $user = User::find($id);

    $password = $request->password != '' ? app('hash')->make($request->password):$user->password;

    $filaname = $user->photo;
    if ($request->hasFile('photo')) {
        $filaname = Str::random(5) . $user->email . '.jpg';
        $file = $request->file('photo');
        $file->move(base_path('public/images'), $filaname); //
        unlink(base_path('public/images/' . $user->photo));
    }

    $user->update([
        'name' => $request->name,
        'identity_id' => $request->identity_id,
        'gender' => $request->gender,
        'address' => $request->address,
        'photo' => $filaname,
        'password' => $password,
        'phone_number' => $request->phone_number,
        'role' => $request->role,
        'status' => $request->status
    ]);
    return response()->json(['status' => 'success']);
}

Adapun rule validasi yang lebih lengkap bisa dilihat di dokumentasi Laravel.

Lakukan uji coba dengan mengirimkan data yang tidak sesuai rule validation, maka kita akan mendapatkan response seperti berikut

aplikasi ekspedisi lumen 6 - validation

Fitur Login User

Jika sebelumnya kita telah membuat fitur untuk protect url tertentu dengan mewajibkan user mengirimkan token, maka pada bagian ini kita akan belajar bagaimana membuat fitur login untuk men-generate api_token apabila credentials user sesuai.

Buka file UserController.php dan tambahkan method

public function login(Request $request)
{
    //VALIDASI INPUTAN USER
    //DENGAN KETENTUAN EMAIL HARUS ADA DI TABLE USERS DAN PASSWORD MIN 6
    $this->validate($request, [
        'email' => 'required|email|exists:users,email',
        'password' => 'required|string|min:6'
    ]);

    //KITA CARI USER BERDASARKAN EMAIL
    $user = User::where('email', $request->email)->first();
    //JIK DATA USER ADA
    //KITA CHECK PASSWORD USER APAKAH SUDAH SESUAI ATAU BELUM
    //UNTUK MEMBANDINGKAN ENCRYPTED PASSWORD DENGAN PLAIN TEXT, KITA BISA MENGGUNAKAN FACADE CHECK
    if ($user && Hash::check($request->password, $user->password)) {
        $token = Str::random(40); //GENERATE TOKEN BARU
        $user->update(['api_token' => $token]); //UPDATE USER TERKAIT
        //DAN KEMBALIKAN TOKENNYA UNTUK DIGUNAKAN PADA CLIENT
        return response()->json(['status' => 'success', 'data' => $token]);
    }
    //JIKA TIDAK SESUAI, BERIKAN RESPONSE ERROR
    return response()->json(['status' => 'error']);
}

Masih dengan file yang sama, tambahkan use statement:

use Illuminate\Support\Facades\Hash;

Untuk menggunakan Facades di Lumen, maka kita perlu mengaktifkannya terlebih dahulu. Buka file bootstrap/app.php dan uncomment code $app->withFacades();.

Bagian terakhir adalah mendefinisikan routing-nya, buka file routes/web.php dan tambahkan code berikut di luar dari block route group karena routing ini tidak akan di-protect dengan token.

$router->post('/login', 'UserController@login');

Lakukan uji coba dengan mengirim request seperti konfigurasi di bawah ini

aplikasi ekspedisi lumen 6 - login

Fitur Reset Password

Dari fitur ini ada dua langkah, yang pertama adalah user mengirimkan request ke endpoint reset password untuk men-generate token reset password sekaligus response dari API akan mengirimkan email ke user terkait beserta link reset password-nya dan langkah kedua adalah user mengirimkan token dari url yang dikliknya dari email beserta password barunya untuk memperbaharui password yang dimilikinya.

Langkah awal, kita buat field reset_token terlebih dahulu ke dalam table users. Dari command line, generate migration baru dengan command.

php artisan make:migration add_field_reset_token_to_users_table

Buka file migration yang baru saja di-generate dan modifikasi menjadi

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddFieldResetTokenToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('reset_token', 40)->nullable()->after('api_token');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('reset_token');
        });
    }
}

Kemudian eksekusi migration tersebut dengan command.

php artisan migrate

Langkah selanjutnya adalah membuat method baru untuk meng-handle generate token reset password, buka file UserController.php dan tambahkan method.

public function sendResetToken(Request $request)
{
    //VALIDASI EMAIL UNTUK MEMASTIKAN BAHWA EMAILNYA SUDAH ADA
    $this->validate($request, [
        'email' => 'required|email|exists:users'
    ]);

    //GET DATA USER BERDASARKAN EMAIL TERSEBUT
    $user = User::where('email', $request->email)->first();
    //LALU GENERATE TOKENNYA
    $user->update(['reset_token' => Str::random(40)]);

    //kirim token via email sebagai otentikasi kepemilikan
    Mail::to($user->email)->send(new ResetPasswordMail($user));

    return response()->json(['status' => 'success', 'data' => $user->reset_token]);
}

Masih dengan file yang sama, tambahkan use statement berikut

use Illuminate\Support\Facades\Mail;
use App\Mail\ResetPasswordMail;

Adapun file ResetPasswordMail.php yang berada di dalam table App/Mail belum tersedia, buat file tersebut dan tambahkan code berikut.

<?php

namespace App\Mail;

use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ResetPasswordMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var Order
     */
    public $user;

    /**
     * Create a new message instance.
     *
     * @return void
     */
  
    //JADI SECARA DEFAULT KITA MEMINTA DATA USER
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        //KEMUDIAN EMAILNYA ME-LOAD VIEW RESET_PASSWORD DAN PASSING DATA USER
        return $this->view('emails.reset_password')->with(['user' => $this->user]);
    }
}

Agar bisa menggunakan fitur Mailable di Lumen, install terlebih dahulu package-nya dengan command

composer require illuminate/mail

Lalu buka file bootstrap/app.php dan tambahkan baris code berikut

$app->register(Illuminate\Mail\MailServiceProvider::class);

$app->configure('mail');
$app->alias('mailer', Illuminate\Mail\Mailer::class);
$app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class);
$app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class);

Kemudian kita buat email konfigurasinya, buat file mail.php di dalam folder config (note: secara default folder ini belum ada, buat foldernya di root project) dan tambahkan code.

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Mail Driver
    |--------------------------------------------------------------------------
    |
    | Laravel supports both SMTP and PHP's "mail" function as drivers for the
    | sending of e-mail. You may specify which one you're using throughout
    | your application here. By default, Laravel is setup for SMTP mail.
    |
    | Supported: "smtp", "sendmail", "mailgun", "ses",
    |            "postmark", "log", "array"
    |
    */

    'driver' => env('MAIL_DRIVER', 'smtp'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Host Address
    |--------------------------------------------------------------------------
    |
    | Here you may provide the host address of the SMTP server used by your
    | applications. A default option is provided that is compatible with
    | the Mailgun mail service which will provide reliable deliveries.
    |
    */

    'host' => env('MAIL_HOST', 'smtp.mailgun.org'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Host Port
    |--------------------------------------------------------------------------
    |
    | This is the SMTP port used by your application to deliver e-mails to
    | users of the application. Like the host we have set this value to
    | stay compatible with the Mailgun e-mail application by default.
    |
    */

    'port' => env('MAIL_PORT', 587),

    /*
    |--------------------------------------------------------------------------
    | Global "From" Address
    |--------------------------------------------------------------------------
    |
    | You may wish for all e-mails sent by your application to be sent from
    | the same address. Here, you may specify a name and address that is
    | used globally for all e-mails that are sent by your application.
    |
    */

    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', '[email protected]'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],

    /*
    |--------------------------------------------------------------------------
    | E-Mail Encryption Protocol
    |--------------------------------------------------------------------------
    |
    | Here you may specify the encryption protocol that should be used when
    | the application send e-mail messages. A sensible default using the
    | transport layer security protocol should provide great security.
    |
    */

    'encryption' => env('MAIL_ENCRYPTION', 'tls'),

    /*
    |--------------------------------------------------------------------------
    | SMTP Server Username
    |--------------------------------------------------------------------------
    |
    | If your SMTP server requires a username for authentication, you should
    | set it here. This will get used to authenticate with your server on
    | connection. You may also set the "password" value below this one.
    |
    */

    'username' => env('MAIL_USERNAME'),

    'password' => env('MAIL_PASSWORD'),

    /*
    |--------------------------------------------------------------------------
    | Sendmail System Path
    |--------------------------------------------------------------------------
    |
    | When using the "sendmail" driver to send e-mails, we will need to know
    | the path to where Sendmail lives on this server. A default path has
    | been provided here, which will work well on most of your systems.
    |
    */

    'sendmail' => '/usr/sbin/sendmail -bs',

    /*
    |--------------------------------------------------------------------------
    | Markdown Mail Settings
    |--------------------------------------------------------------------------
    |
    | If you are using Markdown based email rendering, you may configure your
    | theme and component paths here, allowing you to customize the design
    | of the emails. Or, you may simply stick with the Laravel defaults!
    |
    */

    'markdown' => [
        'theme' => 'default',

        'paths' => [
            resource_path('views/vendor/mail'),
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Log Channel
    |--------------------------------------------------------------------------
    |
    | If you are using the "log" driver, you may specify the logging channel
    | if you prefer to keep mail messages separate from other log entries
    | for simpler reading. Otherwise, the default channel will be used.
    |
    */

    'log_channel' => env('MAIL_LOG_CHANNEL'),

];

Adapun view dari emailnya, buat file reset_password.blade.php di dalam folder resources/views/emails dan tambahkan code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Reset Password</title>
</head>
<body>
    <h1>Hai {{ $user->name }}</h1>
    <p>Kamu sudah melakukan permintaan reset password, silahkan konfirmasi melalui <a href="{{ env('URL_APPS') . '/' . $user->reset_token }}">link ini</a></p>
</body>
</html>

Penjelasan: Perhatikan bagian URL_APPS nantinya akan berisi url dari project client kita, dalam hal ini adalah Nuxt atau Svelte.

Untuk mendefinisikan URL_APPS juga sekaligus akan mendefinisikan konfigurasi email server, buka file .env dan tambahkan code

URL_APPS=http://localhost

MAIL_DRIVER=smtp
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=25
MAIL_USERNAME=EMAIL SERVER ANDA
MAIL_PASSWORD=PASSWORD EMAIL SERVER ANDA
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME=DaengWeb

Note: Isi konfigurasi dari mail server kita untuk meng-handle pengiriman email. Adapun URL_APPS untuk sementara kita isi dengan localhost karena project untuk client belum dibuat, jadi nanti disesuaikan saja.

Sekarang saatnya kita membuat method untuk meng-handle proses update password dengan password baru, buka file UserController.php dan tambahkan code.

public function verifyResetPassword(Request $request, $token)
{
    //VALIDASI PASSWORD HARUS MIN 6 
    $this->validate($request, [
        'password' => 'required|string|min:6'
    ]);

    //CARI USER BERDASARKAN TOKEN YANG DITERIMA
    $user = User::where('reset_token', $token)->first();
    //JIKA DATANYA ADA
    if ($user) {
        //UPDATE PASSWORD USER TERKAIT
        $user->update(['password' => app('hash')->make($request->password)]);
        return response()->json(['status' => 'success']);
    }
    return response()->json(['status' => 'error']);
}

Langkah terakhir adalah mendefinisikan routing dari kedua method di atas, buka file routes/web.php dan tambahkan code.

$router->post('/reset', 'UserController@sendResetToken');
$router->put('/reset/{token}', 'UserController@verifyResetPassword');

Uji coba fitur request token, lakukan konfigurasi seperti berikut

aplikasi ekspedisi lumen 6 - reset password

Adapun request update password, konfigurasinya adalah

aplikasi ekspedisi lumen 6 - update password

Baca Juga: Membuat Aplikasi Ekspedisi Lumen 6 #1: Schema Database

Kesimpulan

Sepanjang artikel ini kita belajar beberapa hal, diantaranya adalah bagaimana menerapkan validation di Lumen, bagaimana membuat fitur login dan reset password di Lumen dan yang terakhir adalah bagaimana mengirimkan email di Lumen.

Adapun dokumentasi code dari artikel ini bisa dilihat di Github.

Category:
Share:

Comments