LaravelAPISanctumMahir5 mnt baca

Sanctum: Token Auth untuk SPA dan Mobile

Terbitkan API token untuk autentikasi tanpa session.

Session vs Token: Kapan Sanctum Dibutuhkan

Session auth bekerja karena browser otomatis mengirim cookie di setiap request ke domain yang sama. Begitu client-mu bukan browser di domain yang sama, misalnya aplikasi mobile Flutter atau SPA React di domain berbeda, cookie session menjadi masalah: tidak terkirim, tidak aman disimpan, dan CSRF protection tidak relevan. Sanctum menyelesaikan ini dengan API token: string acak yang client simpan sendiri dan kirim lewat header Authorization di setiap request. Stateless, sederhana, dan tidak peduli client apa yang memakainya.

Instalasi

bash
php artisan install:api

Perintah ini menginstall Sanctum, membuat migration tabel personal_access_tokens, dan menyiapkan routes/api.php.

Alur Login Token

Pola standarnya: satu endpoint publik menerima email dan password, mengembalikan token. Token itu dipakai untuk semua request berikutnya.

php
// app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $credentials = $request->validate([
            'email' => 'required|email',
            'password' => 'required|string',
        ]);

        if (! Auth::attempt($credentials)) {
            return response()->json(['message' => 'Email atau password salah.'], 401);
        }

        // hapus token lama supaya satu user satu sesi aktif (opsional tapi rapi)
        $request->user()->tokens()->delete();

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

        return response()->json([
            'token' => $token,
            'user' => $request->user()->only(['id', 'name', 'email']),
        ]);
    }

    public function logout(Request $request)
    {
        // cabut HANYA token yang dipakai request ini
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Berhasil logout.']);
    }
}

Route yang butuh login diproteksi middleware auth:sanctum:

php
// routes/api.php
use App\Http\Controllers\Api\AuthController;

Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/me', fn (Request $request) => $request->user());
    Route::apiResource('posts', PostController::class);
});

Abilities: Izin per Token

Token tidak harus all-access. Kamu bisa membatasi apa yang boleh dilakukan tiap token, mirip scope di OAuth:

php
// token yang hanya boleh baca
$token = $user->createToken('read-only', ['post:read'])->plainTextToken;

// token dengan banyak ability
$token = $user->createToken('editor', ['post:read', 'post:create', 'post:update'])->plainTextToken;

Cek di controller atau middleware:

php
// manual
if (! $request->user()->tokenCan('post:create')) {
    abort(403, 'Token tidak punya izin membuat post.');
}

// via middleware (daftar di bootstrap/app.php atau pakai bawaan)
// ->middleware('ability:post:create,post:update')

Ini berguna untuk integrasi pihak ketiga: kasih partner token post:read saja, jadi kalaupun token bocor, damage-nya terbatas pada baca data.

Keamanan Token

Tiga fakta keamanan yang wajib dipahami. Pertama, Sanctum menyimpan token dalam bentuk HASH (SHA-256) di database. Plain text token hanya ditampilkan SEKALI saat createToken(), sesudahnya tidak bisa dilihat lagi, bahkan olehmu. Kalau user kehilangan tokennya, buatkan yang baru, jangan coba "melihat" yang lama.

Kedua, token Sanctum tidak kedaluwarsa secara default. Untuk mengeset expiry saat create, teruskan parameter ketiga: createToken('x', ['*'], now()->addWeek()).

Ketiga, perlakukan token seperti password. Jangan taruh di URL (masuk ke log server), jangan commit ke git. Di mobile, simpan di secure storage (flutter_secure_storage), bukan SharedPreferences biasa.

Jebakan Umum

Pertama, memakai middleware auth (session) di routes/api.php alih-alih auth:sanctum. Hasilnya 401 misterius untuk semua request bertoken, karena guard session mencari cookie yang tidak ada. Kedua, lupa header Accept: application/json di client, sehingga error auth me-redirect ke halaman login HTML alih-alih mengembalikan JSON 401.

Ketiga, Sanctum punya dua mode: token (untuk mobile/API pihak ketiga) dan SPA cookie-based (untuk SPA first-party di subdomain). Jangan campur: SPA first-party pakai cookie + CSRF dengan konfigurasi stateful domain, mobile pakai Bearer token. Memaksa Bearer token untuk SPA first-party berarti kamu kehilangan proteksi CSRF bawaan browser tanpa alasan.

Catatan teknis: Sanctum itu sederhana dengan sengaja: ia bukan OAuth server penuh (itu wilayah Laravel Passport). Untuk 95% kebutuhan (mobile app + SPA + integrasi sederhana), token Sanctum plus abilities sudah lebih dari cukup. Jangan mengadopsi Passport hanya karena terdengar lebih "enterprise" kalau Sanctum menyelesaikan masalahmu dengan 10 baris kode.

Tantangan

Login Token Flow

Implementasikan: POST /api/login -> token, GET /api/me (auth:sanctum) -> data user, POST /api/logout -> token hangus. Test urutan lengkap dengan curl: tanpa token harus 401.