REST API Authentication dengan Laravel Sanctum

ID • 1 year ago • 21 min read • 4848 views
REST API Authentication dengan Laravel Sanctum

REST API Authentication dengan Laravel Sanctum

id21 min read • 4848 views

Rest API Laravel Sanctum - Dalam dunia pengembangan web modern, REST API telah menjadi pendekatan yang populer dalam membangun aplikasi web yang terpisah antara frontend dan backend. Dengan REST API, kita dapat menghubungkan frontend yang berbeda, seperti aplikasi web dan aplikasi seluler, dengan backend yang sama tanpa harus mengulang logika bisnis. Salah satu hal penting yang harus dipertimbangkan dalam membangun REST API adalah keamanan, terutama dalam mengotentikasi user yang ingin mengakses resource tertentu.

Laravel adalah salah satu framework PHP yang populer dan powerful untuk membangun aplikasi web. Laravel Sanctum adalah sebuah package yang ditawarkan oleh Laravel untuk menyediakan autentikasi API yang sederhana namun aman. Dengan Laravel Sanctum, kita dapat dengan mudah mengintegrasikan mekanisme autentikasi API ke dalam aplikasi Laravel kita.

Apa itu Laravel Sanctum?

Laravel Sanctum adalah package autentikasi API yang menyediakan beberapa metode autentikasi yang berbeda untuk aplikasi Laravel. Package ini secara khusus dirancang untuk mendukung autentikasi berbasis token untuk RESTful API.

Sanctum menggunakan konsep stateless tokens, yang berarti tidak ada session yang diatur di sisi server. Setiap request ke API harus menyertakan token autentikasi yang valid untuk mendapatkan akses ke resource tertentu. Sanctum package membuat autentikasi menjadi lebih sederhana dan cocok untuk aplikasi berbasis SPA (Single Page Application) atau aplikasi seluler.

Step 1: Install Laravel

Sebelum menggunakan Laravel Sanctum, pastikan Anda telah menginstal Laravel terlebih dahulu. Jika belum, Anda dapat menginstalnya menggunakan Composer dengan perintah berikut:

composer create-project --prefer-dist laravel/laravel-10

atau menggunakan laravel installer dengan perintah berikut:

laravel new laravel-10

Step 2: Konfigurasi Database

Oke, setelah menyelesaikan step pertama, lanjutkan dengan membuat database baru dan jangan lupa untuk menyesuaikan pada file .env.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_10
DB_USERNAME=root
DB_PASSWORD=

Step 3: Setup Mailer

Dan karena pada artikel ini kita akan membuat skenario, ketika user berhasil register maka akan mengirimkan welcome email notification dan untuk OTP sendernya kita akan menggunakan Gmail SMTP, maka dari itu kita perlu menyesuaikan value untuk key mail pada env.

Baca: Kirim Email di Laravel Menggunakan Gmail SMTP

MAIL_MAILER=smtp
MAIL_HOST=smtp.googlemail.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=qyrqmgbxxxxxxxx
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

Step 4: Setup Model & Migration

Oke, selanjutnya kita perlu melakukan set up pada file users migration. Kita tambahkan dua column yaitu token dan token_expires_at. Kenapa kita perlu menambahkan kedua column tersebut? Karena seperti yang telah saya jelaskan sebelumnya, pada artikel ini kita akan menerapkan login menggunakan kode OTP.

public function up(): void
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->string('token')->nullable();
        $table->timestamp('token_expires_at')->nullable();
        $table->rememberToken();
        $table->timestamps();
    });
}

Jika sudah menambahkan dua column tersebut, sekarang kita bisa menjalankan perintah artisan migrate untuk memigrasi file-file migration menjadi table-table di database yang telah kita buat sebelumnya. Jalankan perintah artisan seperti di bawah ini untuk menjalankan migration.

php artisan migrate

Karena kita mengubah file default users migration dari laravel, maka kita juga perlu menyesuaikannya pada file User model. Silahkan buka file models/User.php, tambahkan token dan token_expires_at pada method $fillable.

protected $fillable = [
    'name',
    'email',
    'password',
    'token',
    'token_expires_at',
];

Step 5: Setup Welcome Email Notification

Kita lanjutkan untuk membuat notification class di laravel project kita. Silahkan jalankan perintah artisan seperti di bawah ini.

php artisan make:notification WelcomeEmailNotification

Perintah tersebut akan menempatkan notification class baru di direktori app/Notifications kita. Setiap notification class berisi method via dan sejumlah variabel method pembuatan pesan, seperti toMail atau toArray, yang mengubah notification menjadi pesan yang disesuaikan untuk chanel tertentu.

Selanjutnya, buka file app/Notifications/WelcomeEmailNotification.php dan sesuaikan kode yang ada menjadi seperti di bawah ini. Disini kita menambahkan construct dan mendefinisikan teks pesan email pada method toMail.

public function __construct(User $user)
{
    $this->user = $user;
}

public function via(object $notifiable): array
{
    return ['mail'];
}

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
                ->line('Welcome to our application, '.$this->user->name)
                ->line('Thank you for using our application!');
}

Step 6: Setup OtpNotification

Selanjutnya kita buat notification class untuk mengirimkan notifikasi OTP ketika ada request kode OTP. Silahkan jalankan perintah artisan seperti di bawah ini untuk membuat class OtpNotification.

php artisan make:notification OtpNotification

Selanjutnya, buka file app/Notifications/OtpNotification.php dan sesuaikan kode yang ada menjadi seperti di bawah ini. Disini kita menambahkan construct dan mendefinisikan teks pesan email pada method toMail.

public function __construct(User $user)
{
    $this->user = $user;
}

public function via(object $notifiable): array
{
    return ['mail'];
}

public function toMail(object $notifiable): MailMessage
{   return (new MailMessage)
            ->greeting('Hello, '.$this->user->name)
            ->line('Your OTP is '.$this->user->token)
            ->line('This OTP will expire in 5 minutes')
            ->line('If you did not request an OTP, no further action is required.')
            ->line('Thank you for using our application!');
}

Step 7: Setup Controller & Resource

Oke, pada step kelima kita akan membuat file Controller baru yang nantinya akan kita gunakan untuk membuat logic-logic. Silahkan jalankan perintah artisan seperti di bawah ini. Perintah tersebut akan membuatkan kita file AuthController.php yang terletak di direktori app/Http/Controllers/API.

php artisan make:controller Api/AuthController

Kemudian, buka file AuthController.php yang baru saja kita generate tersebut dan sesuaikan codenya menjadi seperti di bawah.

<?php

namespace App\Http\Controllers\Api;

use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use Illuminate\Support\Facades\Hash;
use App\Notifications\OtpNotification;
use Illuminate\Support\Facades\Validator;
use App\Notifications\WelcomeEmailNotification;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'name'      => 'required|string|max:255',
                'email'     => 'required|string|email|max:255|unique:users',
                'password'  => 'required|string|min:8|confirmed',
            ]);

            if($validator->fails()){
                return response()->json($validator->errors());       
            }

            $user = User::create([
                'name'          => $request->name,
                'email'         => $request->email,
                'password'      => Hash::make($request->password)
                ]);

            $user->notify(new WelcomeEmailNotification($user));

            $token = $user->createToken('auth_token')->plainTextToken;

            return response()->json([
                'access_token' => $token,
                'token_type'   => 'Bearer',
            ]);

        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }

    public function otp(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'email' => 'required|string|email|max:255|exists:users,email',
            ]);

            if($validator->fails()){
                return response()->json($validator->errors());       
            }

            $user = User::where('email', $request->email)->first();

            $otp = rand(100000, 999999);

            $user->update([
                'token' => $otp,
                'token_expires_at' => now()->addMinutes(5)
            ]);

            $user->notify(new OtpNotification($user));

            return response()->json([
                'message' => 'OTP sent to your email'
            ]);

        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }

    public function login(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'email'     => 'required|string|email|max:255|exists:users,email',
                'password'  => 'required|string|min:8',
            ]);

            if($validator->fails()){
                return response()->json($validator->errors());       
            }

            $user = User::where('email', $request->email)->first();

            if (!Hash::check($request->password, $user->password)) {
                return response()->json([
                    'message' => 'Password mismatch'
                ], 401);
            }

            if ($user->token != $request->otp) {
                return response()->json([
                    'message' => 'OTP mismatch'
                ], 401);
            }

            if ($user->token_expires_at < now()) {
                return response()->json([
                    'message' => 'OTP expired'
                ], 401);
            }

            $user->tokens()->delete();

            $token = $user->createToken('auth_token')->plainTextToken;

            return response()->json([
                'access_token' => $token,
                'token_type'   => 'Bearer',
            ]);

        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }

    public function profile(Request $request)
    {
        try {
            return response()->json(new UserResource($request->user()));
        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }

    public function updateProfile(Request $request)
    {
        try {
            $validator = Validator::make($request->all(), [
                'name'      => 'required|string|max:255',
                'email'     => 'required|string|email|max:255|unique:users,email,'.$request->user()->id,
            ]);

            if($validator->fails()){
                return response()->json($validator->errors());       
            }

            $request->user()->update($request->only('name', 'email'));

            return response()->json(new UserResource($request->user()));
        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }

    public function logout(Request $request)
    {
        try {
            $request->user()->tokens()->delete();

            return response()->json([
                'message' => 'Logged out'
            ]);
        } catch (\Throwable $th) {
            return response()->json([
                'message' => 'Something went wrong',
                'error' => $th->getMessage()
            ], 500);
        }
    }
}

Pada AuthController ini kita mempunyai beberapa method diantaranya; register, otp, profile, updateProfile, login dan logout. Method-method tersebut akan dijelaskan di bawah ini.

Register

public function register(Request $request)
{
    try {
        $validator = Validator::make($request->all(), [
            'name'      => 'required|string|max:255',
            'email'     => 'required|string|email|max:255|unique:users',
            'password'  => 'required|string|min:8|confirmed',
        ]);

        if($validator->fails()){
            return response()->json($validator->errors());       
        }

        $user = User::create([
            'name'          => $request->name,
            'email'         => $request->email,
            'password'      => Hash::make($request->password)
            ]);

        $user->notify(new WelcomeEmailNotification($user));

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'access_token' => $token,
            'token_type'   => 'Bearer',
        ]);

    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Method register hanya berisikan fungsi validasi dan insert to database dari apa yang telah diinputkan pada endpoint register. Setelah data disimpan di tabel users, kita jalankan fungsi $user->notify(new WelcomeEmailNotification($user)); untuk mengirimkan welcome email notification ke email yang digunakan saat register. Setelah itu kita generate bearer token dengan fungsi $token = $user->createToken('auth_token')->plainTextToken;. Method createToken me-return instance Laravel\Sanctum\NewAccessToken. API token di-hash menggunakan hashing SHA-256 sebelum disimpan di database, tetapi kita dapat mengakses plain text token value menggunakan property plainTextToken dari instance NewAccessToken.

Get OTP Code

public function otp(Request $request)
{
    try {
        $validator = Validator::make($request->all(), [
            'email' => 'required|string|email|max:255|exists:users,email',
        ]);

        if($validator->fails()){
            return response()->json($validator->errors());       
        }

        $user = User::where('email', $request->email)->first();

        $otp = rand(100000, 999999);

        $user->update([
            'token' => $otp,
            'token_expires_at' => now()->addMinutes(5)
        ]);

        $user->notify(new OtpNotification($user));

        return response()->json([
            'message' => 'OTP sent to your email'
        ]);

    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Method otp berfungsi untuk generate otp code (random) kemudian memperbarui value property otp dan token_expires_at dari akun user yang digunakan. Kemudian akan mengirimkan OTP notification via email menggunakan fungsi $user->notify(new OtpNotification($user));

Login

public function login(Request $request)
{
    try {
        $validator = Validator::make($request->all(), [
            'email'     => 'required|string|email|max:255|exists:users,email',
            'password'  => 'required|string|min:8',
        ]);

        if($validator->fails()){
            return response()->json($validator->errors());       
        }

        $user = User::where('email', $request->email)->first();

        if (!Hash::check($request->password, $user->password)) {
            return response()->json([
                'message' => 'Password mismatch'
            ], 401);
        }

        if ($user->token != $request->otp) {
            return response()->json([
                'message' => 'OTP mismatch'
            ], 401);
        }

        if ($user->token_expires_at < now()) {
            return response()->json([
                'message' => 'OTP expired'
            ], 401);
        }

        $user->tokens()->delete();

        $token = $user->createToken('auth_token')->plainTextToken;

        return response()->json([
            'access_token' => $token,
            'token_type'   => 'Bearer',
        ]);

    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Method login me-validasi email, password dan token atau otp code. Jika valid, maka akan menjalankan fungsi hapus token-token yang dimiliki oleh user tersebut dan generate token baru.

Get Profile

public function profile(Request $request)
{
    try {
        return response()->json(new UserResource($request->user()));
    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Untuk method profile hanya berisikan fungsi untuk menampilkan data user menggunakan class UserResource.

Update Profile

public function updateProfile(Request $request)
{
    try {
        $validator = Validator::make($request->all(), [
            'name'      => 'required|string|max:255',
            'email'     => 'required|string|email|max:255|unique:users,email,'.$request->user()->id,
        ]);

        if($validator->fails()){
            return response()->json($validator->errors());       
        }

        $request->user()->update($request->only('name', 'email'));

        return response()->json(new UserResource($request->user()));
    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Method updateProfile hanya berisikan fungsi update profile biasa. Sebelum menjalankan fungsi update data, terdapat validasi name dan email. Dan setelah data name dan email terdapat, maka akan me-return data user menggunakan class UserResource.

Logout

public function logout(Request $request)
{
    try {
        $request->user()->tokens()->delete();

        return response()->json([
            'message' => 'Logged out'
        ]);
    } catch (\Throwable $th) {
        return response()->json([
            'message' => 'Something went wrong',
            'error' => $th->getMessage()
        ], 500);
    }
}

Saat method ini dipanggil, maka akan menghapus token milik user tersebut dari database. Sehingga, jika user tersebut ingin mengakses halaman yang telah diproteksi dengan middleware auth:sanctum, dia harus login terlebih dahulu untuk mendapatkan access token lagi.

Seperti yang telah saya sebutkan di awal, pada artikel ini saya juga akan mengimplementasikan eloquent api resources. Untuk membuat file api resources, kita bisa menjalankan perintah artisan seperti di bawah ini.

php artisan make:resource UserResource

Perintah tersebut akan membuatkan kita file UserResource dan terletak didirektori app/Http/Resources.

Buka file yang baru saja digenerate tersebut, pada function toArray silahkan ubah menjadi seperti contoh kode di bawah ini.

public function toArray(Request $request): array
{
    return [
        'id'    => $this->id,
        'name'  => $this->name,
        'email' => $this->email,
    ];
}

Pada Class UserResource, kita hanya ingin mengambil data id, name dan email user.

Step 8: Setup Route

Setelah selesai melakukan set up Controller, langkah selanjutnya atau pada langkah ke enam ini, kita akan melakukan set up route. Silahkan buka file routes/api.php dan sesuaikan code yang ada menjadi seperti di bawah ini.

use App\Http\Controllers\Api\AuthController;
....
....
....
Route::controller(AuthController::class)->group(function () {
    Route::post('register', 'register');
    Route::post('login', 'login');
    
    Route::middleware('auth:sanctum')->group(function () {

        Route::prefix('profile')->group(function () {
            Route::get('/', 'profile');
            Route::post('/', 'update');
        });

        Route::post('logout', 'logout');
    });
});

Kita bisa mengelompokkan route dalam group middleware auth:sanctum untuk route-route yang perlu diproteksi seperti get profile, update profile dan logout.

Step 9: Testing

Oke, setelah menyelesaikan langkah-langkah di atas, sekarang waktunya untuk pengujian. Untuk menguji Rest API authentication yang telah kita buat dengan Sanctum di laravel 10 ini, kita bisa menggunakan Postman.

Register

Pengujian yang pertama adalah menguji fitur register. Silahkan buka Postman, buat request baru dengan method Post, isi URL dengan 127.0.0.1:{port}/api/register atau laravel-sanctum.test/api/register. Kemudian pada tab Body, pilih type raw JSON dan tambahkan data json seperti gambar di bawah ini, lalu klik Send. Jika request yang kamu kirimkan berhasil divalidasi, maka response yang akan diberikan akan seperti di bawah ini.

laravel sanctum rest api register

Nah, seperti yang telah saya sebutkan pada step kelima. Jika user berhasil melakukan register, maka akan langsung digenerate access token. Kita akan membutuhkan token tersebut untuk mengakses route yang telah diproteksi dengan middleware auth:sanctum.

Selain itu, jika kita berhasil register, maka sistem akan mengirimkan welcome email notification seperti di bawah ini.

laravel welcome email

 

Get Profile

Pengujian yang selanjutnya adalah melihat data profile user. Seperti yang telah saya jelaskan sebelumya, untuk route get profile ini telah kita proteksi menggunakan middleware auth:sanctum, sehingga untuk mengaksesnya, kita perlu menambahkan token yang telah kita dapatkan saat melakukan register atau login.

Silahkan buat request baru dengan method GET, isi URL dengan 127.0.0.1:{port}/api/profile atau laravel-sanctum.test/api/profile. Pada tab Authorization, pilih type Bearer Token dan isi tokennya dengan token yang telah didapatkan saat melakukan register atau login. Jika token sudah benar, klik Send, maka akan menampilkan response berupa data dari user tersebut seperti pada gambar di bawah ini.

laravel sanctum rest api get profile

Update Profile

OK, Next. Kita akan melakukan pengujian pada endpoint update profile. Sama seperti pada endpoint get profile, pada update profile ini kita juga memerlukan token untuk mengaksesnya. Silahkan buat request baru dengan method POST, isi URL dengan 127.0.0.1:{port}/api/profile atau laravel-sanctum.test/api/profile. Pada tab Authorization, pilih type Bearer Token dan isi tokennya dengan token yang telah didapatkan saat melakukan register atau login.

laravel sanctum rest api update profile

Kemudian, buka tab Body dan pilih type raw JSON. Isi key dan value sesuai dengan apa yang ingin kamu ubah dari data profile tersebut, lalu klik Send. Jika request berhasil divalidasi maka data dari profile kamu akan berubah.

Logout

Selanjutnya Kita akan melakukan pengujian pada endpoint logout. Pada endpoint logout ini kita memerlukan token untuk mengaksesnya. Silahkan buat request baru dengan method POST, isi URL dengan 127.0.0.1:{port}/api/logout atau laravel-sanctum.test/api/logout. Pada tab Authorization, pilih type Bearer Token dan isi tokennya dengan token yang telah didapatkan saat melakukan register atau login. Jika kita klik send, maka semua token yang dimiliki user tersebut pada table personal_access_tokens akan terhapus. Dan jika kita ingin mengakses get profile, update profile dan logout, kita perlu login kembali untuk mendapatkan token.

laravel sanctum rest api logout

Get OTP

Seperti yang telah saya jelaskan di awal, pada artikel ini kita akan menerapkan login menggunakan kode OTP. Jadi, sebelum login, kita perlu request kode OTP terlebih dahulu untuk dapat kita gunakan saat login ke aplikasi kita. Silahkan buat request baru dengan method POST, isi URL dengan 127.0.0.1:{port}/api/otp atau laravel-sanctum.test/api/otp. Kemudian, klik tab body pilih raw JSON dan isi key email lalu klik Send. Jika request yang kamu kirimkan berhasil divalidasi, maka akan menampilkan response berisikan message seperti pada gambar di bawah ini.

laravel sanctum rest api get otp

Setelah endpoint get OTP dijalankan, maka sistem akan mengirimkan kode OTP via email dengan isi pesan email seperti gambar di bawah ini.

laravel sanctum rest api get otp

Kode OTP inilah yang akan kita gunakan untuk login ke aplikasi atau untuk mengisi key otp pada endpoint login.

Login

Dan yang terakhir, kita akan melakukan pengujian pada endpoint login. Silahkan buat request baru dengan method POST, isi URL dengan 127.0.0.1:{port}/api/login atau laravel-sanctum.test/api/login. Kemudian, klik tab body dan isi key email, password dan otp lalu klik Send. Jika request yang kamu kirimkan berhasil divalidasi, maka akan menampilkan response berisikan message dan bearer token seperti pada gambar di bawah ini.

laravel sanctum rest api login

Kesimpulan

Dalam artikel ini, kami telah membahas tentang Laravel Sanctum, sebuah package yang menyediakan autentikasi API yang sederhana namun aman untuk aplikasi Laravel. Dengan menggunakan Sanctum, kita dapat dengan mudah mengintegrasikan autentikasi berbasis token ke dalam aplikasi Laravel, menjadikannya lebih aman dan sesuai dengan standar autentikasi REST API.

Penting untuk selalu menjaga keamanan aplikasi web dan API, dan Laravel Sanctum adalah salah satu cara yang bagus untuk melakukannya. Dengan langkah-langkah yang telah dijelaskan di atas, Anda dapat dengan cepat mulai menggunakan autentikasi API di proyek Laravel Anda dan melindungi resource dari akses yang tidak sah.

Series: Laravel 10 Rest API using Sanctum
Published on July 27, 2023
Last updated on April 21, 2025
Loading Comments

If you like this post and want to support us, you can support us via buymeacoffee or trakteer.