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 | Teks acak di header permintaan |
| 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 fungsi tambah (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 bersih — dapat bukti masuk
Cuplikan di bawah hampir sama dengan yang gagal — bedanya hanya sandi benar. 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 atau alat yang memanggil API (bukan di server). 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 proyek Laravel, cuplikan tipikal memakai penjaga Form Request + controller login. 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). Di request kita pakai nama isiansandisupaya mudah dibaca; di database Laravel kolomnya biasanyapassword— isinya sama, namanya saja beda.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 seperti ini:
Authorization: Bearer kartu-ab12cd34...
Authorization = kotak di header untuk “siapa yang meminta”. Bearer artinya “ini bukti yang saya bawa”. Tanpa itu (atau salah): Laravel menjawab 401.
Controller fungsi tambah (store) tetap tipis seperti di Controller, Service & Eloquent (#58) — yang baru: pintu di depannya sudah dikunci.
Pola Dasar — Auth API
-
1
Rapikan alur dulu Penjaga + controller/service sudah berdiri (lihat Controller, Service & Eloquent (#58)).
-
2
Sediakan login Cek email/sandi — gagal =
401, sukses = bukti masuk. -
3
Lindungi pintu yang sensitif Misalnya
POST /api/bukuwajib bukti; katalog publik boleh tetap terbuka. -
4
Bawa bukti di setiap permintaan terlindungi Header Authorization (tempat bukti) + Bearer + teks bukti.
-
5
Baru satukan jadi proyek utuh Berikutnya: Capstone API Perpustakaan (#60) — 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 (alat banding sandi terenkripsi di Laravel) | Simpan sandi terenkripsi; bandingkan lewat Hash::check |
| Semua route dikunci | Katalog publik ikut pemeriksa pintu (middleware) | Kunci hanya yang sensitif (misalnya POST) |
Latihan singkat
- Ubah demo: tambah kasus “bukti palsu” (teks acak) dan pastikan status tetap 401.
- Jelaskan ke teman (tanpa jargon): beda 401 dan 422 dengan analogi loket perpustakaan.
- Tulis satu kalimat: apa yang terjadi dari login sukses sampai
POST /api/bukudengan 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). Urutannya boleh beda, tapi perannya tetap 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 API Perpustakaan (#60) — 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) · 8/8 Capstone Laravel selesai · stack Laravel 4/5 · prasyarat: Controller, Service & Eloquent (#58) LIVE. Berikutnya: Capstone API Perpustakaan (#60).