Sanctum 401 Unauthorized: Checklist Lengkap

Saat memakai Laravel Sanctum, error 401 Unauthorized biasanya berarti route yang kamu akses memang terlindungi, tetapi Laravel tidak berhasil mengenali user yang sedang login. Dari sisi developer, ini sering membingungkan karena login terlihat berhasil, token sudah ada, tetapi request berikutnya tetap dianggap guest.

Akar masalah Sanctum hampir selalu jatuh ke salah satu dari dua mode autentikasi yang tertukar: token API biasa atau SPA auth berbasis session/cookie. Kalau kamu mencampur dua mode ini tanpa konfigurasi yang tepat, 401 akan terus muncul.

Kapan Masalah Ini Biasanya Muncul?

  • Request ke route auth:sanctum selalu mendapat 401
  • Token sudah dibuat, tetapi endpoint /api/user tetap mengembalikan guest
  • Aman di Postman, tetapi gagal di browser
  • Login SPA sukses, tetapi request berikutnya tetap dianggap belum login
  • User login, tetapi route dengan ability tertentu tetap ditolak

Penyebab Paling Umum

1. Header Authorization: Bearer ... Tidak Terkirim

Kalau kamu memakai personal access token, setiap request harus membawa header token yang benar.

curl -H "Authorization: Bearer 1|token-sanctum" \
     -H "Accept: application/json" \
     http://localhost:8000/api/user

2. Token yang Dipakai Bukan Token Terbaru atau Sudah Tidak Valid

Sering terjadi saat frontend masih menyimpan token lama di local storage atau environment.

3. Kamu Sebenarnya Sedang Membangun SPA Auth, Bukan Token Auth

Untuk SPA berbasis cookie/session, Sanctum butuh konfigurasi tambahan seperti:

  • domain stateful
  • CORS yang benar
  • request CSRF cookie
  • browser mengirim cookie ke backend

Kalau salah satu langkah ini terlewat, hasilnya sering 401 atau 419.

4. Route atau Guard Tidak Sesuai

Kadang route dipasang di file yang salah atau middleware lain ikut mengganggu alur autentikasi.

5. Token Ability Tidak Cocok

Kalau route mensyaratkan ability tertentu, token yang tidak punya ability itu akan ditolak.

Langkah Diagnosis

  1. Tentukan dulu mode yang kamu pakai:
    • token API biasa
    • SPA auth dengan cookie/session
  2. Uji endpoint sederhana seperti /api/user.
  3. Cek request headers di browser devtools atau Postman:
    • apakah header Authorization ada?
    • apakah cookie session ikut terkirim?
  4. Pastikan route target memang memakai auth:sanctum.
  5. Kalau memakai SPA, cek apakah endpoint /sanctum/csrf-cookie sudah dipanggil sebelum login.
Pisahkan Dua Skenario Ini

Kalau request datang dari mobile app, Postman, atau frontend terpisah, biasanya kamu butuh Bearer token. Kalau request datang dari SPA first-party, kamu biasanya butuh cookie + CSRF flow. Jangan campur keduanya.

Langkah Fix

1. Untuk Token API: Kirim Header dengan Benar

<?php

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Contoh request:

curl -H "Authorization: Bearer 1|abc123token" \
     -H "Accept: application/json" \
     http://localhost:8000/api/user

2. Untuk Token API: Buat Token Setelah Login

<?php

Route::post('/login', function (Request $request) {
    $credentials = $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

    if (!Auth::attempt($credentials)) {
        return response()->json(['message' => 'Login gagal'], 401);
    }

    $token = $request->user()->createToken('api-token')->plainTextToken;

    return response()->json([
        'token' => $token,
    ]);
});

Contoh flow frontend:

await fetch('http://localhost:8000/sanctum/csrf-cookie', {
  credentials: 'include',
});

const xsrfToken = decodeURIComponent(
  document.cookie
    .split('; ')
    .find((row) => row.startsWith('XSRF-TOKEN='))
    ?.split('=')[1] ?? '',
);

await fetch('http://localhost:8000/login', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-XSRF-TOKEN': xsrfToken,
  },
  body: JSON.stringify({
    email: '[email protected]',
    password: 'secret',
  }),
});

4. Pastikan Route Ditaruh di Tempat yang Tepat

  • Endpoint login web biasanya ada di routes/web.php
  • Endpoint API JSON biasanya ada di routes/api.php

Yang penting bukan cuma filenya, tetapi guard dan flow autentikasinya konsisten.

5. Cek Ability Token Jika Dipakai

<?php

$token = $user->createToken('mobile-app', ['produk:create'])->plainTextToken;

Kalau route butuh ability tertentu, token harus dibuat dengan ability yang sesuai.

Contoh Sebelum dan Sesudah

Sebelum

await fetch('/api/user');

Request tidak membawa token maupun cookie, jadi Laravel membaca user sebagai guest.

Sesudah

await fetch('/api/user', {
  headers: {
    Authorization: `Bearer ${token}`,
    Accept: 'application/json',
  },
});

Error Umum

Aman di Postman, Gagal di Browser

Biasanya flow SPA belum lengkap: cookie tidak ikut terkirim, domain stateful salah, atau CSRF belum diambil.

Token Ada, Tapi Tetap 401

Periksa apakah frontend masih memakai token lama, token salah copy, atau header Authorization tidak benar-benar terkirim.

Berubah Jadi 419 Page Expired

Ini biasanya tanda bahwa kamu sedang masuk ke flow session/cookie, tetapi konfigurasi CSRF atau session belum beres.

Pencegahan

  1. Putuskan sejak awal: token auth atau SPA auth.
  2. Uji endpoint /api/user setiap kali setup auth berubah.
  3. Pastikan frontend selalu mengirim Accept: application/json.
  4. Jangan mencampur penyimpanan token lama dan baru.
  5. Dokumentasikan flow login yang dipakai tim agar tidak tertukar.

Bacaan Terkait

FAQ

Kenapa login berhasil, tetapi /api/user tetap 401?

Karena request lanjutan kemungkinan tidak membawa bukti autentikasi yang benar: token Bearer atau cookie session.

Apakah Sanctum hanya untuk SPA?

Tidak. Sanctum bisa dipakai untuk dua skenario: personal access token dan SPA auth first-party.

Kalau request aman di Postman, tetapi gagal di browser, itu sinyal kuat bahwa masalahnya ada di cookie, domain stateful, atau CORS.