Pendahuluan — alur sudah rapi, siapa yang boleh masuk?

Di Controller, Service & Eloquent (#58) kamu sudah punya alur rapi: penjaga input, pengatur kode, layanan, dan penyimpanan. Artikel ini adalah #59 (ini) — langkah keempat stack Laravel di Seri 4.

Ide barunya: tidak semua orang boleh memanggil API. Kita butuh otentikasi — memastikan “siapa kamu” sebelum pintu dibuka. Awam: bukti masuk (sering disebut token) seperti kartu anggota perpustakaan.

Awam: bayangkan loket khusus staf. Siapa saja boleh lihat katalog umum. Tapi menambah buku hanya untuk yang sudah login dan membawa kartu anggota (bukti masuk). Tanpa kartu: ditolak — bukan karena data kotor, tapi karena belum diizinkan.

Prasyarat: sudah baca Controller, Service & Eloquent (#58) — paham controller/service dan status JSON. Domain tetap perpustakaan mini. Pakai Laravel 11+ — ide login + bukti masuk di sini berlaku di versi modern.

Istilah — Auth, login, bukti masuk

Istilah Arti awam Contoh singkat
Otentikasi (auth) Memastikan “siapa kamu” sebelum akses Login email + kata sandi
Login Proses membuktikan identitas (biasanya email/sandi) POST /api/login
Bukti masuk (token) Kartu sementara setelah login — dibawa di permintaan berikutnya String acak di header
Status 401 “Belum diizinkan” — pintu ketemu, tapi kamu belum terbukti 401 tanpa bukti / bukti salah
Pemeriksa pintu Lapisan yang cek bukti masuk sebelum controller jalan (sering disebut middleware) Cek header dulu, baru store

Jangan hafal semua dulu. Cukup: login mengeluarkan bukti, permintaan berikutnya membawa bukti, tanpa bukti = 401.

Bedakan dari 422 di Request & Form Request (#57): 422 = data kotor; 401 = kamu belum diizinkan.

Kenapa belum langsung paket Laravel?

Kenapa belum langsung paket bukti masuk Laravel (sering disebut Sanctum) / login bawaan? Karena ide “cek identitas dulu, baru lanjut” bisa dirasakan di PHP biasa. Kalau ide-nya sudah “klik”, cuplikan Laravel nanti terasa seperti bungkus yang sama.

<?php
// Anggota sederhana (bukan database sungguhan).
$anggota = [
    "email" => "staf@perpustakaan.test",
    "sandi" => "rahasia123",
];

$input = [
    "email" => "staf@perpustakaan.test",
    "sandi" => "salah",
];

header("Content-Type: application/json; charset=utf-8");

if ($input["email"] !== $anggota["email"] || $input["sandi"] !== $anggota["sandi"]) {
    http_response_code(401);
    echo json_encode(["pesan" => "Belum diizinkan — email atau sandi salah"], JSON_UNESCAPED_UNICODE), PHP_EOL;
    exit;
}

$bukti = "kartu-".bin2hex(random_bytes(8));
http_response_code(200);
echo json_encode(["ok" => true, "bukti_masuk" => $bukti], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output:

{"pesan":"Belum diizinkan — email atau sandi salah"}

Awam: status 401 artinya “pintu ketemu, tapi kamu belum terbukti”. Sandi di contoh disimpan polos supaya mudah dibaca — di dunia nyata sandi disimpan terenkripsi (tidak dibaca apa adanya). bin2hex(random_bytes(...)) hanya cara membuat teks acak untuk kartu — tidak perlu dihafal dulu.

Login -> bukti masuk -> permintaan terlindungi Login email + sandi Cek login siapa kamu? Bukti masuk kartu sementara API terlindungi 201 atau 401
Bukti masuk bukan “rahasia abadi”. Ia dikeluarkan setelah login, lalu dibawa di permintaan berikutnya.

Login bersih — dapat bukti masuk

Kalau email dan sandi cocok, sistem mengeluarkan bukti:

<?php
$anggota = [
    "email" => "staf@perpustakaan.test",
    "sandi" => "rahasia123",
];

$input = [
    "email" => "staf@perpustakaan.test",
    "sandi" => "rahasia123",
];

header("Content-Type: application/json; charset=utf-8");

if ($input["email"] !== $anggota["email"] || $input["sandi"] !== $anggota["sandi"]) {
    http_response_code(401);
    echo json_encode(["pesan" => "Belum diizinkan — email atau sandi salah"], JSON_UNESCAPED_UNICODE), PHP_EOL;
    exit;
}

$bukti = "kartu-".bin2hex(random_bytes(8));
http_response_code(200);
echo json_encode(["ok" => true, "bukti_masuk" => $bukti], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output (bentuknya mirip; angka di bukti berubah tiap jalan):

{
    "ok": true,
    "bukti_masuk": "kartu-ab12cd34ef56..."
}

Awam: simpan bukti ini di sisi pemanggil (aplikasi / alat uji), lalu kirim lagi saat menambah buku.

Pintu terlindungi — cek bukti dulu

Sebelum controller menyimpan buku, pemeriksa pintu membaca bukti masuk:

<?php
$buktiValid = "kartu-abc123";
$buktiDariHeader = ""; // kosong = belum bawa kartu

header("Content-Type: application/json; charset=utf-8");

if ($buktiDariHeader === "" || $buktiDariHeader !== $buktiValid) {
    http_response_code(401);
    echo json_encode(["pesan" => "Belum diizinkan — bawa bukti masuk"], JSON_UNESCAPED_UNICODE), PHP_EOL;
    exit;
}

http_response_code(201);
echo json_encode(["ok" => true, "buku" => ["judul" => "Belajar PHP"]], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output:

{"pesan":"Belum diizinkan — bawa bukti masuk"}

Awam: ini “pemeriksa pintu” — ide yang sama dengan middleware di Laravel: cek dulu, baru lanjut ke pengatur kode.

Laravel — login & bukti masuk (cuplikan)

Di project Laravel, cuplikan tipikal memakai penjaga Form Request + layanan auth. File ini bukan dijalankan dengan php file.php:

<?php
// Cuplikan Laravel (bukan file mandiri) — login mengeluarkan bukti masuk.
namespace App\Http\Controllers;

use App\Http\Requests\LoginRequest;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function login(LoginRequest $request): JsonResponse
    {
        $user = User::query()->where('email', $request->validated('email'))->first();

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

        // createToken = cara Laravel (Sanctum) membuat bukti masuk API.
        $bukti = $user->createToken('api-perpustakaan')->plainTextToken;

        return response()->json(['ok' => true, 'bukti_masuk' => $bukti], 200);
    }
}

Awam:

  • LoginRequest = penjaga isian login (ide Form Request dari Request & Form Request (#57))
  • JsonResponse = tipe jawaban “ini JSON” (boleh diabaikan dulu kalau masih asing)
  • Hash::check = bandingkan sandi input dengan sandi tersimpan (yang sudah dienkripsi)
  • createToken(...) = buat bukti masuk; plainTextToken = teks bukti yang dikirim ke pemanggil (hanya tampil sekali)
  • Sanctum = paket Laravel yang biasa dipakai untuk bukti masuk API — detail pasang-pasangnya bisa dipelajari nanti; di sini cukup paham alurnya
<?php
// Cuplikan Laravel — route terlindungi.
use App\Http\Controllers\BukuController;
use Illuminate\Support\Facades\Route;

// auth:sanctum = pemeriksa pintu: wajib bawa bukti masuk yang valid.
Route::post('/api/buku', [BukuController::class, 'store'])
    ->middleware('auth:sanctum');

Awam: pemanggil biasanya mengirim header Authorization: Bearer <bukti>. Authorization = kotak di header untuk “siapa yang meminta”. Bearer artinya “ini bukti yang saya bawa”. Tanpa itu (atau salah): Laravel menjawab 401.

Controller store tetap tipis seperti di Controller, Service & Eloquent (#58) — yang baru: pintu di depannya sudah dikunci.

Pola Dasar — Auth API

  1. 1
    Rapikan alur dulu Penjaga + controller/service sudah berdiri (lihat Controller, Service & Eloquent (#58)).
  2. 2
    Sediakan login Cek email/sandi — gagal = 401, sukses = bukti masuk.
  3. 3
    Lindungi pintu yang sensitif Misalnya POST /api/buku wajib bukti; katalog publik boleh tetap terbuka.
  4. 4
    Bawa bukti di setiap permintaan terlindungi Header Authorization (tempat bukti) + Bearer + teks bukti.
  5. 5
    Baru satukan jadi proyek utuh Berikutnya: Capstone — merangkai routing, validasi, controller/service, dan auth jadi API perpustakaan lengkap.

Kode lengkap — demo mandiri

Simpan sebagai laravel_auth_api_demo.php, lalu jalankan php laravel_auth_api_demo.php:

<?php
declare(strict_types=1);

$anggota = [
    "email" => "staf@perpustakaan.test",
    "sandi" => "rahasia123",
];

$buktiAktif = null;

function login(array $input, array $anggota): array
{
    if (($input["email"] ?? "") !== $anggota["email"] || ($input["sandi"] ?? "") !== $anggota["sandi"]) {
        return ["status" => 401, "body" => ["pesan" => "Belum diizinkan — email atau sandi salah"]];
    }

    $bukti = "kartu-".bin2hex(random_bytes(4));

    return ["status" => 200, "body" => ["ok" => true, "bukti_masuk" => $bukti]];
}

function tambahBuku(?string $buktiHeader, ?string $buktiAktif): array
{
    if ($buktiHeader === null || $buktiHeader === "" || $buktiHeader !== $buktiAktif) {
        return ["status" => 401, "body" => ["pesan" => "Belum diizinkan — bawa bukti masuk"]];
    }

    return ["status" => 201, "body" => ["ok" => true, "buku" => ["judul" => "Belajar PHP", "tahun" => 2024]]];
}

function demo(string $judul, callable $aksi): void
{
    echo "=== {$judul} ===", PHP_EOL;
    $hasil = $aksi();
    echo "status: ", $hasil["status"], PHP_EOL;
    echo json_encode($hasil["body"], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL, PHP_EOL;
}

demo("Login kotor -> 401", function () use ($anggota) {
    return login(["email" => "staf@perpustakaan.test", "sandi" => "salah"], $anggota);
});

demo("Login bersih -> 200 + bukti", function () use ($anggota, &$buktiAktif) {
    $hasil = login(["email" => "staf@perpustakaan.test", "sandi" => "rahasia123"], $anggota);
    if (($hasil["body"]["bukti_masuk"] ?? null) !== null) {
        $buktiAktif = $hasil["body"]["bukti_masuk"];
    }

    return $hasil;
});

demo("Tanpa bukti -> 401", function () use (&$buktiAktif) {
    return tambahBuku(null, $buktiAktif);
});

demo("Dengan bukti -> 201", function () use (&$buktiAktif) {
    return tambahBuku($buktiAktif, $buktiAktif);
});

Awam: demo(...) hanya membungkus output di terminal. callable = sesuatu yang bisa dipanggil seperti fungsi. Baris declare(strict_types=1); membuat tipe data lebih ketat — boleh diikuti, tidak wajib dihafal dulu. Alur yang penting: login gagal/sukses, lalu pintu dengan/ tanpa bukti.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan awam
Selalu 401 padahal sudah login Bukti tidak dikirim / salah header Kirim Authorization: Bearer <bukti>
Bingung 401 vs 422 Mencampur “belum diizinkan” dengan “data kotor” 401 = identitas; 422 = isian
Sandi tersimpan polos Tidak memakai Hash (pembanding sandi terenkripsi) Simpan sandi terenkripsi lewat Hash
Semua route dikunci Katalog publik ikut pemeriksa pintu (middleware) Kunci hanya yang sensitif (misalnya POST)

Latihan singkat

  1. Ubah demo: tambah kasus “bukti palsu” (string acak) dan pastikan status tetap 401.
  2. Jelaskan ke teman (tanpa jargon): beda 401 dan 422 dengan analogi loket perpustakaan.
  3. Tulis satu kalimat: apa yang terjadi dari login sukses sampai POST /api/buku dengan bukti masuk.

FAQ singkat

Apa bedanya auth dan validasi?
Validasi cek “apakah isian masuk akal”. Auth cek “apakah kamu yang berhak”. Keduanya sering berurutan: bukti dulu, baru isian — atau sebaliknya tergantung desain, tapi perannya beda.

Haruskah semua API pakai bukti masuk?
Tidak. Baca katalog boleh publik. Menambah/mengubah data biasanya dikunci.

Token / bukti masuk aman digeser ke orang lain?
Tidak. Siapa yang punya bukti = dianggap kamu. Jaga seperti kunci.

Lanjut ke mana?
Berikutnya: Capstone — merangkai routing, validasi, controller/service/Eloquent, dan auth jadi API perpustakaan yang utuh.

Kesimpulan

Kamu sudah menambah kunci di depan alur: login mengeluarkan bukti masuk, pintu sensitif memeriksa bukti, tanpa bukti = 401. Alur dari Controller, Service & Eloquent (#58) tetap dipakai — hanya pintunya yang dikunci.

Seri 4 progress: langkah #59 (ini) · 7/8 menuju Capstone Laravel · stack Laravel 4/5 · prasyarat: Controller, Service & Eloquent (#58) LIVE. Berikutnya: Capstone API perpustakaan (merangkai semua langkah).