Sanctum — token API
Sanctum menawarkan dua mode yang sering tertukar: token personal untuk klien pihak ketiga, dan autentikasi berbasis cookie untuk SPA di domain yang sama. Memilih yang salah menghasilkan sesi yang selalu gagal.
Intisari
- Mode token: klien mengirim
Authorization: Bearer …. Untuk mobile dan integrasi. - Mode SPA: memakai cookie sesi Laravel biasa. Untuk frontend di domain yang sama.
- Token disimpan sebagai hash; nilai aslinya hanya muncul sekali saat dibuat.
- Ability membatasi apa yang boleh dilakukan sebuah token — dan tidak pernah bisa melebihi izin pemiliknya.
- Pakai Passport hanya kalau kamu memang harus jadi server OAuth2 bagi aplikasi pihak ketiga.
Memasang
php artisan install:api # memasang Sanctum + membuat routes/api.php
class User extends Authenticatable
{
use HasApiTokens; // ← ditambahkan oleh install:api
}
Mode 1 — token personal
// Menerbitkan token
$token = $user->createToken(
name: 'aplikasi-mobile',
abilities: ['artikel:baca', 'artikel:tulis'],
expiresAt: now()->addDays(30),
);
return ['token' => $token->plainTextToken]; // HANYA muncul sekarang
curl https://contoh.test/api/artikel \
-H "Authorization: Bearer 1|AbCdEf..." \
-H "Accept: application/json"
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::get('/artikel', [ArtikelController::class, 'index']);
Route::post('/artikel', [ArtikelController::class, 'store'])
->middleware('ability:artikel:tulis');
});
Nilai token hanya ada satu kali. Database menyimpan hash-nya, persis seperti password. Kalau pengguna
kehilangan token, tidak ada cara memulihkannya — yang bisa dilakukan hanya mencabut dan menerbitkan yang baru.
Ini fitur: bocornya isi tabel personal_access_tokens tidak memberi penyerang satu pun token yang
bisa dipakai.
Mode 2 — SPA di domain yang sama
SANCTUM_STATEFUL_DOMAINS=portal.test,localhost:5173
SESSION_DOMAIN=.portal.test
# 1. Ambil cookie CSRF lebih dulu
GET /sanctum/csrf-cookie
# 2. Login seperti form biasa — hasilnya cookie sesi
POST /login
# 3. Panggilan berikutnya cukup mengirim cookie; tidak ada token sama sekali
| Mode token | Mode SPA | |
|---|---|---|
| Untuk | Mobile, integrasi pihak ketiga, skrip | Frontend di domain yang sama |
| Dikirim lewat | Header Authorization | Cookie |
| CSRF | Tidak relevan | Wajib — ambil /sanctum/csrf-cookie |
| Kedaluwarsa | Ditentukan saat dibuat | Mengikuti masa sesi |
| Dicabut dengan | Hapus baris token | Logout |
Kesalahan konfigurasi paling umum di mode SPA. Frontend berjalan di
localhost:5173 tapi domain itu tidak terdaftar di SANCTUM_STATEFUL_DOMAINS — sehingga
Sanctum memperlakukannya sebagai klien token, tidak menemukan header Authorization, dan menjawab
401 pada setiap permintaan. Gejalanya: "login berhasil tapi permintaan berikutnya selalu 401".
Mengelola token
$user->tokens; // daftar
$user->tokens()->where('name', 'lama')->delete();
$user->currentAccessToken()->delete(); // logout perangkat ini saja
$user->tokens()->delete(); // cabut semuanya
// Di dalam kode aplikasi
if ($request->user()->tokenCan('artikel:tulis')) { /* ... */ }
Ability membatasi, tidak memberi. Token dengan ability artikel:hapus milik pengguna yang
policy-nya tidak mengizinkan penghapusan tetap tidak bisa menghapus. Urutannya: policy dulu, ability kemudian.
Ability berguna untuk memberi klien pihak ketiga bagian dari izin pemiliknya, bukan untuk memperluasnya.
Praktik yang layak diikuti
- Beri masa berlaku pada setiap token; token abadi adalah kredensial abadi.
- Satu token per perangkat atau per integrasi, dengan nama yang bermakna — supaya bisa dicabut satu per satu.
- Ability yang sesempit mungkin; jangan menerbitkan token serba bisa untuk satu kebutuhan sempit.
- Batasi laju rute penerbitan token — ia adalah endpoint yang menarik untuk ditebak-tebak.
- Cabut seluruh token saat pengguna mengganti password.
Latihan: buat endpoint POST /api/token yang memverifikasi email dan password lalu
menerbitkan token dengan ability artikel:baca saja. Panggil GET /api/artikel dengan
token itu (harus berhasil), lalu POST /api/artikel (harus 403). Terakhir, cabut tokennya dan
pastikan permintaan berikutnya menjadi 401.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.