Laravel Sanctum menyediakan dua cara autentikasi API: token API untuk aplikasi mobile, integrasi pihak ketiga, atau skrip, dan autentikasi berbasis cookie untuk SPA (Vue, React) yang berada di domain yang sama dengan backend. Pilih token kalau klien bukan browser di domainmu; pilih cookie untuk SPA karena lebih aman dari pencurian token lewat JavaScript. Contoh di bawah memakai Laravel 12.
Instalasi Laravel Sanctum
php artisan install:api
Perintah ini memasang Sanctum, membuat routes/api.php, dan migration tabel personal_access_tokens. Tambahkan trait di model User:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Cara 1: Token untuk Aplikasi Mobile
Endpoint login yang mengeluarkan token
// routes/api.php
Route::post('/login', [ApiLoginController::class, 'store'])->middleware('throttle:5,1');
Route::middleware('auth:sanctum')->group(function () {
Route::get('/me', fn (Request $request) => new UserResource($request->user()));
Route::apiResource('orders', OrderController::class);
Route::post('/logout', [ApiLoginController::class, 'destroy']);
});
class ApiLoginController extends Controller
{
public function store(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
'device_name' => ['required', 'string', 'max:100'],
]);
$user = User::where('email', $credentials['email'])->first();
if (! $user || ! Hash::check($credentials['password'], $user->password)) {
throw ValidationException::withMessages(['email' => 'Email atau password salah.']);
}
$token = $user->createToken($credentials['device_name'], ['orders:read', 'orders:create']);
return response()->json(['token' => $token->plainTextToken]);
}
public function destroy(Request $request): Response
{
$request->user()->currentAccessToken()->delete();
return response()->noContent();
}
}
Klien mengirim token di header setiap request:
Authorization: Bearer 1|abc123...
Accept: application/json
Header Accept: application/json penting agar error validasi dan autentikasi dikembalikan sebagai JSON, bukan redirect ke halaman login.
Ability: batasi hak token
// cek di controller
abort_unless($request->user()->tokenCan('orders:create'), 403);
// atau di route dengan middleware ability (daftarkan alias di bootstrap/app.php)
Route::post('/orders', [OrderController::class, 'store'])->middleware('abilities:orders:create');
Token untuk integrasi gudang cukup punya hak baca pesanan, bukan hak menghapus.
Masa berlaku token
// config/sanctum.php
'expiration' => 60 * 24 * 30, // 30 hari, dalam menit
Jadwalkan pembersihan token kedaluwarsa dengan perintah sanctum:prune-expired di scheduler.
Cara 2: Cookie untuk SPA
Untuk SPA di domain atau subdomain yang sama (misalnya app.tokoku.id dan api.tokoku.id):
# .env
SANCTUM_STATEFUL_DOMAINS=app.tokoku.id
SESSION_DOMAIN=.tokoku.id
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
Alur di frontend:
// 1. ambil cookie CSRF
await axios.get('/sanctum/csrf-cookie', { withCredentials: true });
// 2. login seperti form biasa
await axios.post('/login', { email, password }, { withCredentials: true });
// 3. request berikutnya otomatis terautentikasi lewat cookie session
const { data } = await axios.get('/api/me', { withCredentials: true });
Atur CORS agar supports_credentials bernilai true dan origin SPA diizinkan. Tidak ada token yang disimpan di localStorage, sehingga lebih aman dari serangan XSS yang mencuri token.
Response yang Konsisten dengan API Resource
class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'number' => $this->number,
'total' => $this->total,
'status' => $this->status,
'created_at' => $this->created_at->toIso8601String(),
];
}
}
Resource mencegah kolom sensitif ikut terkirim tanpa sengaja. Untuk memeriksa bentuk response saat debugging, rapikan dengan Format JSON.
Test Endpoint dengan Pest
use Laravel\Sanctum\Sanctum;
it('menampilkan pesanan milik pengguna', function () {
$user = User::factory()->has(Order::factory()->count(2))->create();
Sanctum::actingAs($user, ['orders:read']);
$this->getJson('/api/orders')->assertOk()->assertJsonCount(2, 'data');
});
it('menolak request tanpa token', function () {
$this->getJson('/api/orders')->assertUnauthorized();
});
Kesalahan yang Sering Terjadi
- Hanya autentikasi, tanpa otorisasi. Token valid belum berarti boleh melihat pesanan orang lain. Gunakan policy:
$this->authorize('view', $order). - Login tanpa rate limit, membuka peluang tebak password massal.
- Token tidak pernah kedaluwarsa dan tidak bisa dicabut dari sisi pengguna.
- Menyimpan token SPA di localStorage padahal SPA berada di domain yang sama; pakai mode cookie.
Celah lain yang sering muncul di API dibahas di OWASP Top 10 untuk Laravel.
Langkah Berikutnya
Kalau klienmu butuh mengambil data dengan bentuk yang sangat bervariasi, timbang alternatifnya di GraphQL vs REST API. Untuk frontend SPA yang mengonsumsi API ini dengan tipe data yang aman, lihat TypeScript untuk Laravel developer.