Pendahuluan — kartu anggota di perpustakaan mini

Artikel ini adalah #61 (ini) di Seri 4: Pemrograman Web Lanjut v2. Di Controller, Service & Eloquent Laravel (#60) kamu sudah punya loket + dapur + tabel. Sekarang langkah 6/8: belajar Auth API dasar — login yang mengeluarkan kartu anggota (token), lalu melindungi pintu yang hanya boleh dibuka pemegang kartu.

Awam: tanpa kartu, siapa saja bisa “masuk ruang staf”. Dengan kartu, tamu harus login dulu. Hari ini kita pasang mesin kartu (Sanctum), buat pintu login, dan uji satu pintu terlindungi.

Domain tetap perpustakaan mini. Belum Capstone penuh (baca + login + tambah) — itu langkah berikutnya.

Prasyarat: sudah selesai Controller, Service & Eloquent Laravel (#60) — proyek perpustakaan-api jalan, GET /api/buku pernah OK. Fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder, .env & Artisan Laravel (#57). Pakai Laravel 13+ — butuh PHP 8.3+.

Spesifikasi fitur — apa yang kita kuasai?

Daftar singkat yang bisa kamu centang di akhir artikel:

  1. Memasang Laravel Sanctum dari nol (Composer + publish + migrate).
  2. Membuat pintu POST /api/login yang mengembalikan token (kartu anggota).
  3. Melindungi pintu contoh GET /api/saya supaya tanpa kartu = 401.
  4. Menguji dengan terminal kedua + curl.exe / PowerShell (bukan bilah alamat browser saja).

Awam: urutan nyaman: buka alat -> pasang Sanctum -> siapkan user uji -> buat login -> lindungi pintu -> uji 401 lalu sukses.

Istilah — ringkas untuk kartu anggota

Istilah Arti awam Contoh di artikel ini
Auth / login Membuktikan “saya staf yang sah” POST /api/login
Token / kartu anggota String rahasia yang dibawa di setiap permintaan terlindungi Header Authorization: Bearer …
Sanctum Paket Laravel untuk kartu anggota API composer require laravel/sanctum
401 Belum diizinkan — kartu kosong / salah GET /api/saya tanpa Bearer

Urutan belajar: kenali kartu -> pasang mesin kartu -> buat login -> lindungi pintu -> uji.

Kenapa auth sekarang?

Validasi di Request & Form Request: Menjaga Input API (#59) menjaga slip kotor. Controller, Service & Eloquent Laravel (#60) merapikan loket. Tanpa auth, pintu “ruang staf” masih terbuka untuk siapa saja yang tahu URL.

Awam: satpam cek slip; petugas kartu cek siapa yang boleh masuk. Hari ini fokus siapa — bukan Capstone penuh.

Artikel ini tetap install-dari-nol untuk mesin kartu: Sanctum dipasang dari nol di bawah — tidak mengandalkan “sudah ada di laptop orang lain”.

Alur — login sampai pintu terlindungi

Alur: Login -> Token -> Pintu terlindungi Alur: Login -> Kartu (token) -> Pintu /api/saya Terminal 2 POST /api/login AuthController keluarkan token Bearer kartu di header /api/saya 200 / 401 Terminal 1 tetap menjalankan php artisan serve (lampu toko). Tanpa Bearer: 401 Belum diizinkan. Dengan Bearer sah: JSON profil singkat. Urutan #61 (ini) — belum Capstone tambah buku ber-auth.
#61 (ini): login -> token -> pintu terlindungi.

Persiapan — alat yang kamu buka

Alat yang dipakai di artikel ini (fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder, .env & Artisan Laravel (#57) — unduhan baru hanya lewat Composer di bawah):

  • Explorer — memastikan folder proyek perpustakaan-api dan melihat file Controller / Model yang dibuat Artisan.
  • Terminal — Laragon: menu Terminal · XAMPP: tombol Shell. Jangan asal CMD/PowerShell dari Start Menu (PATH PHP/Composer bisa hilang).
  • Terminal kedua — wajib: terminal pertama = php artisan serve. Terminal kedua = Composer, Artisan lain, dan curl.exe / PowerShell untuk uji POST/GET ber-token.
  • Editor teks — Notepad / VS Code — edit Model User, AuthController, routes/web.php. Tip: notepad app\Http\Controllers\AuthController.php dari terminal kedua (ganti nama file sesuai yang mau dibuka).
  • Browser — opsional hari ini. Pintu login & uji token pakai terminal (seperti POST di Request & Form Request: Menjaga Input API (#59)). Browser berguna hanya untuk cek lampu toko masih hidup.

Cara buka terminal kedua (baru pertama kali buka dua terminal sekaligus? ini caranya, jangan tutup yang pertama): Laragon — klik menu Terminal sekali lagi di jendela utama Laragon, sebuah jendela terminal baru akan muncul terpisah dari yang pertama. XAMPP — di XAMPP Control Panel, klik tombol Shell sekali lagi, jendela Shell kedua akan terbuka. Kedua jendela boleh hidup bersamaan — jendela pertama tetap menjalankan php artisan serve, jendela kedua kamu pakai untuk mengetik perintah lain.

Buka terminal Laragon/Shell XAMPP (terminal pertama), masuk folder proyek:

cd C:\laragon\www\perpustakaan-api

Di XAMPP biasanya: cd C:\xampp\htdocs\perpustakaan-api. Sesuaikan path jika foldermu beda.

Nyalakan lampu toko di terminal pertama:

php artisan serve

Biarkan jendela itu hidup. Buka terminal kedua (caranya sudah dijelaskan di atas), cd ke folder proyek yang sama — di sini kamu mengetik Composer, Artisan, dan uji curl.exe.

Awam: Terminal 1 = lampu toko. Terminal 2 = tangan memasang Sanctum + menguji kartu. Editor = menulis login. Browser = boleh dicek sebentar, bukan alat utama uji token.

Pasang Sanctum dari nol

Di terminal kedua (folder perpustakaan-api, lampu toko tetap hidup di terminal pertama):

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Awam: baris 1 mengunduh mesin kartu. Baris 2 menyalin pengaturan Sanctum ke proyekmu. Baris 3 membuat tabel token di database (SQLite dari fondasi denah Struktur Folder, .env & Artisan Laravel (#57) sudah cukup).

Install-dari-nol: jika composer tidak dikenal, kembali ke Instal PHP, Composer & Proyek Laravel (#56) — pakai Shell XAMPP/Laragon yang sama seperti saat create-project.

Buka model user: notepad app\Models\User.php. Pastikan class memakai trait kartu Sanctum (tempel di dalam class, dekat use HasFactory jika ada):

// Cuplikan app/Models/User.php — di dalam class User
use Laravel\Sanctum\HasApiTokens;

// ... di tubuh class:
use HasApiTokens, HasFactory, Notifiable;

Awam: sesuaikan dengan kerangka file yang sudah ada — jangan hapus namespace / nama class. Yang penting: HasApiTokens ikut dipakai supaya user bisa mengeluarkan token.

Siapkan satu user uji

Masih di terminal kedua. Buat / perbarui satu staf uji dengan perintah satu tembakan (tidak perlu masuk mode percakapan):

php artisan tinker --execute="\App\Models\User::updateOrCreate(['email'=>'staf@perpustakaan.test'], ['name'=>'Staf Mini','password'=>bcrypt('password')]);"

Awam: ini bukan chat panjang. tinker --execute="…" = minta Artisan menjalankan satu baris PHP lalu selesai. Email uji: staf@perpustakaan.test · sandi: password (hanya untuk belajar di laptopmu).

Controller login — loket kartu

Di terminal kedua:

php artisan make:controller AuthController

Buka file: notepad app\Http\Controllers\AuthController.php. Jangan hapus namespace dan kerangka class. Tambahkan use di atas class, lalu method login di dalam class:

// Cuplikan AuthController — loket login
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

public function login(Request $request): JsonResponse
{
    $data = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required', 'string'],
    ]);

    $user = User::query()->where('email', $data['email'])->first();

    if (! $user || ! Hash::check($data['password'], $user->password)) {
        return response()->json(['message' => 'Belum diizinkan — email/sandi salah'], 401);
    }

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

    return response()->json([
        'message' => 'Login berhasil — simpan kartu (token)',
        'token' => $token,
        'token_type' => 'Bearer',
    ]);
}

Awam: kalau email/sandi salah = 401 + pesan jelas. Kalau benar = JSON berisi token. Simpan token itu; nanti ditempel di header.

Pintu saya — contoh terlindungi

Tambahkan method di Controller yang sama (atau Controller kecil lain). Cuplikan untuk AuthController:

// Cuplikan AuthController — siapa yang sedang masuk
public function saya(Request $request): JsonResponse
{
    $user = $request->user();

    return response()->json([
        'message' => 'Kartu diterima',
        'data' => [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
        ],
    ]);
}

Buka notepad routes\web.php. Tambahkan (di bawah route yang sudah ada):

// Cuplikan routes/web.php — login + pintu ber-kartu
use App\Http\Controllers\AuthController;

Route::post('/api/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->get('/api/saya', [AuthController::class, 'saya']);

Simpan. Pastikan serve masih hidup di terminal pertama.

Awam: auth:sanctum = satpam kartu di pintu /api/saya. Tanpa header Bearer yang sah, tamu ditolak.

Cek izin api/* — supaya login tidak ditolak 419

Laravel punya satpam lobi bawaan bernama CSRF yang melindungi formulir web: ia menolak semua kiriman POST yang tidak datang dari halaman browser milik situs itu sendiri. Karena kita menguji POST /api/login dari terminal (bukan browser), tanpa izin khusus jawabannya adalah {"message":"CSRF token mismatch."} dengan kode 419 — bukan token.

Kalau di dalam withMiddleware sudah ada 'api/*' (biasanya dari Request & Form Request: Menjaga Input API (#59)) — lewati langkah ini. Penjelasan panjangnya ada di artikel itu.

Belum ada? Pastikan terminal kedua sudah di folder proyek (cd ke perpustakaan-api), lalu notepad bootstrap\app.php. Jangan menempel blok ->withMiddleware(...) baru di dalam fungsi yang sudah ada. Kerja di dalam fungsi itu: hapus // kalau masih ada, biarkan baris lain, lalu tempel hanya:

// Tempel di dalam withMiddleware yang sudah ada — jangan buat withMiddleware baru
$middleware->preventRequestForgery(except: [
    'api/*',
]);

Hasil yang benar kira-kira begini (boleh ada baris lain di sekitarnya):

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'api/*',
    ]);
})

Awam: except = “kecuali”. Di Laravel 13 nama resminya preventRequestForgery; nama lama validateCsrfTokens masih sama artinya.

Setiap kali menyimpan bootstrap\app.php, matikan serve di terminal pertama (Ctrl+C) lalu php artisan serve lagi — file itu hanya dibaca saat aplikasi mulai.

Tenang, kartunya tetap aman. Pagar yang menjaga /api/saya adalah petugas kartu Sanctum yang kamu pasang di artikel ini — CSRF itu satpam lobi untuk formulir browser, jenis yang berbeda.

Uji di terminal kedua

Jangan andalkan bilah alamat browser untuk POST login. Ikuti pola uji di Request & Form Request: Menjaga Input API (#59).

Opsi A — curl.exe (Windows 10/11 biasanya sudah punya; ketik curl.exe agar tidak tertukar di PowerShell):

1) Login — ambil token

curl.exe -s -X POST http://127.0.0.1:8000/api/login ^
  -H "Content-Type: application/json" ^
  -H "Accept: application/json" ^
  -d "{\"email\":\"staf@perpustakaan.test\",\"password\":\"password\"}"

Awam — salin token: di jawaban JSON cari kunci "token". Salin hanya string di antara tanda kutip (panjang, tanpa spasi di ujung). Jangan salin kata Bearer dari JSON — kata Bearer ditulis nanti di header. Ganti GANTI_DENGAN_TOKEN di perintah berikutnya dengan string itu.

Awam — cara salin teks dari terminal Windows: blok teks dengan klik kiri lalu tahan sambil digeser (klik-drag), lepas tombol mouse untuk menyalin otomatis. Tempel dengan klik kanan di jendela terminal (bukan Ctrl+V, terminal bawaan Windows kadang tidak mendukungnya).

2) Tanpa kartu — harus 401

curl.exe -s http://127.0.0.1:8000/api/saya ^
  -H "Accept: application/json"

Kamu harus melihat pesan seperti “Belum diizinkan” / unauthenticated — bukan profil staf.

3) Dengan kartu — harus 200 + data nama/email

curl.exe -s http://127.0.0.1:8000/api/saya ^
  -H "Accept: application/json" ^
  -H "Authorization: Bearer GANTI_DENGAN_TOKEN"

Opsi B — PowerShell (kalau kutip curl ribet):

# Login — catat .token dari hasil
$login = Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/login `
  -ContentType 'application/json' -Headers @{ Accept = 'application/json' } `
  -Body '{"email":"staf@perpustakaan.test","password":"password"}'
$login.token
# Dengan kartu — ganti isi string token
Invoke-RestMethod -Method Get -Uri http://127.0.0.1:8000/api/saya `
  -Headers @{ Accept = 'application/json'; Authorization = 'Bearer GANTI_DENGAN_TOKEN' }

Opsi C — alat uji API berjendela (Postman / Insomnia — opsional). Login: metode POST, URL http://127.0.0.1:8000/api/login, body JSON email+password, header Accept + Content-Type = application/json. Lalu GET /api/saya dengan header Authorization: Bearer … (tempel token). Ide sama: tanpa kartu ditolak, dengan kartu diterima.

Awam: baris Accept: application/json meminta jawaban JSON (bukan halaman HTML error panjang). ^ di CMD = lanjut baris; di PowerShell pakai backtick ` atau satu baris. Selalu curl.exe, bukan alias curl.

Pola Dasar — empat langkah kartu bersih

  1. 1
    Buka alat
    Terminal 1 serve · Terminal 2 Composer/Artisan/curl · Editor.
  2. 2
    Pasang Sanctum
    composer require · publish · migrate · HasApiTokens.
  3. 3
    Login + pintu
    POST /api/login · GET /api/saya + auth:sanctum.
  4. 4
    Uji 401 lalu 200
    Tanpa Bearer ditolak; dengan Bearer menampilkan profil singkat.

Demo kartu anggota — file mandiri

Latihan ide tanpa mengubah proyek Laravel:

  1. Buka editor, buat file baru, tempel cuplikan di bawah, simpan sebagai laravel_auth_api_dasar_demo.php (boleh di Desktop).
  2. Buka terminal di folder file itu (Explorer: Shift+klik kanan -> “Open in Terminal” / “Buka di Terminal”, atau cd manual). Pastikan php -v jalan.
  3. Jalankan: php laravel_auth_api_dasar_demo.php — layar mensimulasikan login gagal/sukses dan cek kartu.

File ini mensimulasikan peran — tidak mengubah proyek Laravel-mu:

<?php

declare(strict_types=1);

function demo(): void
{
    $staf = ['email' => 'staf@perpustakaan.test', 'password' => 'password'];

    echo "=== Simulasi Auth API dasar ===", PHP_EOL;

    $salah = loginSimulasi('salah@x.test', 'x', $staf);
    echo "Login salah -> ", $salah['message'], " (HTTP ", $salah['status'], ")", PHP_EOL;

    $ok = loginSimulasi('staf@perpustakaan.test', 'password', $staf);
    echo "Login OK -> token: ", $ok['token'], PHP_EOL;

    $tanpa = cekKartuSimulasi(null);
    echo "Tanpa kartu -> ", $tanpa['message'], " (HTTP ", $tanpa['status'], ")", PHP_EOL;

    $dengan = cekKartuSimulasi($ok['token'] ?? null);
    echo "Dengan kartu -> ", $dengan['message'], " · ", ($dengan['data']['email'] ?? '-'), PHP_EOL;
    echo PHP_EOL, "Langkah sungguhan: Sanctum -> user uji -> AuthController -> route -> curl.exe.", PHP_EOL;
}

/**
 * Simulasi login — kembalikan status + pesan (+ token jika sukses).
 */
function loginSimulasi(string $email, string $password, array $staf): array
{
    if ($email !== $staf['email'] || $password !== $staf['password']) {
        return ['status' => 401, 'message' => 'Belum diizinkan — email/sandi salah'];
    }

    return [
        'status' => 200,
        'message' => 'Login berhasil',
        'token' => 'kartu-demo-'.substr(sha1($email), 0, 8),
    ];
}

/**
 * Simulasi cek kartu di pintu terlindungi.
 */
function cekKartuSimulasi(?string $token): array
{
    if ($token === null || $token === '') {
        return ['status' => 401, 'message' => 'Belum diizinkan'];
    }

    return [
        'status' => 200,
        'message' => 'Kartu diterima',
        'data' => ['email' => 'staf@perpustakaan.test'],
    ];
}

demo();

Awam: demo() hanya latihan di terminal. Setelah paham, kerjakan langkah Sanctum di folder perpustakaan-api. declare(strict_types=1); membuat tipe lebih ketat — boleh diikuti, tidak wajib dihafal.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan awam
CSRF token mismatch / kode 419 saat login Izin api/* di bootstrap\app.php belum dipasang, atau serve belum dinyalakan ulang Pasang preventRequestForgery(except: ['api/*']) (jangan timpa baris lain), lalu Ctrl+C dan php artisan serve lagi
composer tidak dikenal Terminal Start Menu / PATH Pakai Laragon Terminal atau Shell XAMPP — lihat Persiapan
Class HasApiTokens not found Sanctum belum require / trait belum di-use Ulangi composer require laravel/sanctum + cek User.php
401 terus saat login User uji belum dibuat / sandi beda Jalankan ulang perintah tinker --execute di atas
401 di /api/saya meski sudah login Header Bearer kosong / token salah tempel Salin token utuh dari kunci "token"; format Authorization: Bearer … (satu spasi setelah Bearer)
Jawaban HTML panjang / bukan JSON Lupa header Accept: application/json Tambahkan header Accept seperti di Opsi A
curl aneh di PowerShell Alias curl tertukar Pakai curl.exe, Opsi B PowerShell, atau Opsi C
Connection refused serve mati Nyalakan lagi di terminal pertama setelah cd ke proyek

Latihan

  1. Jalankan demo PHP di atas — pastikan kasus login salah = 401 simulasi.
  2. Di proyek: pasang Sanctum, buat user uji, login, lalu bandingkan respons tanpa vs dengan Bearer.
  3. Jelaskan ke teman: beda singkat “satpam slip (validasi)” dan “satpam kartu (auth)” dengan bahasa toko.

FAQ

Terminal mana yang harus dibuka?
Laragon: menu Terminal · XAMPP: tombol Shell. Satu jendela untuk serve, jendela kedua untuk Composer/Artisan/curl.exe.

Harus Postman?
Tidak. curl.exe / PowerShell cukup. Alat berjendela (Opsi C) hanya jika kutip di terminal terasa ribet.

Token yang mana yang disalin?
Dari jawaban login, kunci "token" — string panjang di antara kutip. Jangan salin seluruh JSON. Jangan tulis ulang kata Bearer dua kali.

Apa itu Bearer tanpa istilah sulit?
Bearer = “bawa kartu di header”. Isinya token dari login. Tanpa itu, pintu /api/saya menolak.

Kenapa pakai Sanctum, bukan session web biasa?
API JSON sering dipanggil dari terminal/aplikasi lain. Token mudah dibawa di header. Session cookie lebih cocok halaman web ber-form.

Apa hubungan dengan artikel loket?
Controller, Service & Eloquent Laravel (#60) merapikan daftar buku. #61 (ini) menambah siapa yang boleh masuk pintu staf.

Ke mana setelah ini?
Berikutnya: Capstone: API Perpustakaan (Baca + Login + Tambah) (#62) — menggabungkan baca katalog, login, dan tambah buku dengan kartu.

Kesimpulan

Kamu sudah memasang Auth API dasar: Sanctum dari nol, login mengeluarkan token, pintu /api/saya terlindungi, diuji lewat terminal saat artisan serve hidup. Ini langkah 6/8 jalur Laravel di Seri 4.

Seri 4 progress: langkah #61 (ini) · 6/8 jalur Laravel · prasyarat: Controller, Service & Eloquent Laravel (#60) LIVE. Berikutnya: Capstone: API Perpustakaan (Baca + Login + Tambah) (#62).