Setup Laravel Sanctum untuk REST API: Panduan Auth + CRUD Lengkap

Kenapa Butuh Token Auth untuk API?

Kalau aplikasi client (misal aplikasi desktop, mobile, atau SPA) perlu mengambil dan mengubah data dari Laravel lewat internet, cara paling aman adalah lewat REST API dengan autentikasi token — bukan koneksi database langsung. Alasannya sederhana: koneksi database langsung mengharuskan client menyimpan kredensial database mentah, membuka port database ke publik, dan melewati semua validasi/business logic yang ada di Laravel.

Laravel Sanctum adalah paket resmi Laravel untuk menerbitkan token API sederhana. Client login sekali, dapat token, lalu token itu dipakai di header Authorization untuk semua request berikutnya.


Contoh Kasus: CRUD Sederhana "Notes"

Supaya gampang diikuti, dokumentasi ini pakai contoh resource sederhana: Notes (catatan) dengan field title dan body. Pola yang sama bisa dipakai untuk resource lain apa pun.

1. Install Sanctum

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Perintah migrate ini membuat tabel personal_access_tokens, tempat semua token yang diterbitkan akan disimpan.

2. Aktifkan Token di Model User

Tambahkan trait HasApiTokens di app/Models/User.php:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // ...
}

3. Buat Model & Migration untuk Notes

php artisan make:model Note -m

Isi file migration-nya:

public function up(): void
{
    Schema::create('notes', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->cascadeOnDelete();
        $table->string('title');
        $table->text('body')->nullable();
        $table->timestamps();
    });
}

Isi model app/Models/Note.php:

class Note extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'body', 'user_id'];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

Jalankan migration:

php artisan migrate

4. Buat Controller Auth

php artisan make:controller Api/AuthController

Isi app/Http/Controllers/Api/AuthController.php:

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

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

        $user = User::where('email', $request->email)->first();

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['Email atau password salah.'],
            ]);
        }

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

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

    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Logged out']);
    }
}

5. Buat Controller CRUD Notes

php artisan make:controller Api/NoteController

Isi app/Http/Controllers/Api/NoteController.php:

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Note;
use Illuminate\Http\Request;

class NoteController extends Controller
{
    // GET /api/notes
    public function index(Request $request)
    {
        return $request->user()->notes()->latest()->get();
    }

    // POST /api/notes
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'body' => 'nullable|string',
        ]);

        $note = $request->user()->notes()->create($validated);

        return response()->json($note, 201);
    }

    // GET /api/notes/{note}
    public function show(Note $note)
    {
        return $note;
    }

    // PUT/PATCH /api/notes/{note}
    public function update(Request $request, Note $note)
    {
        $validated = $request->validate([
            'title' => 'sometimes|required|string|max:255',
            'body' => 'nullable|string',
        ]);

        $note->update($validated);

        return response()->json($note);
    }

    // DELETE /api/notes/{note}
    public function destroy(Note $note)
    {
        $note->delete();

        return response()->json(['message' => 'Note deleted']);
    }
}

Tambahkan relasi notes() di model User.php:

public function notes(): HasMany
{
    return $this->hasMany(Note::class);
}

6. Daftarkan Route

Di routes/api.php:

use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\NoteController;
use Illuminate\Support\Facades\Route;

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

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::apiResource('notes', NoteController::class);
});
Penting: route /login harus di luar grup middleware('auth:sanctum'). Logikanya: user belum punya token saat mencoba login, jadi belum bisa lolos middleware yang mewajibkan token.

Untuk Laravel 11 ke atas, pastikan juga routes/api.php sudah terdaftar di bootstrap/app.php:

->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)

7. Cek Route Sudah Terdaftar

php artisan route:list --path=api

Harus muncul baris untuk POST api/login, GET api/notes, POST api/notes, GET api/notes/{note}, PUT api/notes/{note}, DELETE api/notes/{note}, dan POST api/logout.

8. Testing API

Cara paling cepat memastikan API bekerja sebelum sibuk bikin aplikasi client: test murni pakai curl atau PowerShell. Kalau semua langkah ini lolos, artinya masalah apa pun yang muncul nanti di sisi client sudah pasti bukan dari API.

Login (dapat token)

curl -i -X POST http://localhost:8000/api/login \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"email":"user@example.com","password":"rahasia","device_name":"test-curl"}'

Response yang diharapkan (HTTP 200):

{"token":"1|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","user":{"id":1,"name":"...","email":"user@example.com"}}

Create Note

Ganti <token> dengan token hasil login diatas.

curl -i -X POST http://localhost:8000/api/notes \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"title":"Catatan pertama","body":"Isi catatan"}'

List Notes

curl -i http://localhost:8000/api/notes \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json"

Update Note

curl -i -X PUT http://localhost:8000/api/notes/1 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"title":"Judul diubah"}'

Delete Note

curl -i -X DELETE http://localhost:8000/api/notes/1 \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json"

Logout & Pastikan Token Mati

curl -i -X POST http://localhost:8000/api/logout \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json"

Coba request ulang ke /api/notes pakai token yang sama setelah logout — harus mendapat HTTP 401 Unauthenticated, bukti token sudah benar-benar dicabut.

Testing di PowerShell (Windows)

Perlu diperhatikan: curl di PowerShell adalah alias untuk Invoke-WebRequest, yang syntax-nya berbeda dari curl asli (tidak mendukung -H/-d). Ada dua solusi:

Opsi 1 — panggil curl asli secara eksplisit dengan curl.exe:

curl.exe -i -X POST http://localhost:8000/api/login -H "Content-Type: application/json" -H "Accept: application/json" -d '{\"email\":\"user@example.com\",\"password\":\"rahasia\"}'

Opsi 2 — pakai Invoke-RestMethod native PowerShell (lebih direkomendasikan, tanpa escaping ribet):

$body = @{
    email = "user@example.com"
    password = "rahasia"
    device_name = "test-ps"
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri "http://localhost:8000/api/login" `
    -Method POST -ContentType "application/json" -Body $body

$token = $response.token

Invoke-RestMethod -Uri "http://localhost:8000/api/notes" `
    -Method GET -Headers @{ Authorization = "Bearer $token" }

Troubleshooting Umum

Gejala Kemungkinan Penyebab
Route login 404 Not Found Baris Route::post('/login', ...) belum ditambahkan, atau tidak sengaja ditaruh di dalam grup middleware('auth:sanctum')
HTTP 500 saat login, error terkait HasApiTokens Trait HasApiTokens belum ditambahkan ke model User
Response HTML, bukan JSON Header Accept: application/json belum dikirim, atau route ternyata terdaftar di web.php, bukan api.php
HTTP 401 Unauthenticated di endpoint yang di-protect Token salah/kadaluarsa, atau guard di config/sanctum.php belum sesuai
Semua route di api.php tidak terdaftar sama sekali Di Laravel 11+, baris api: __DIR__.'/../routes/api.php' belum ada di bootstrap/app.php

Ringkasan Alur

  1. Install & setup Sanctum, tambahkan HasApiTokens ke model User.
  2. Buat model, migration, dan controller untuk resource (contoh: Notes).
  3. Daftarkan route: login di luar grup, endpoint CRUD di dalam grup middleware('auth:sanctum').
  4. Test satu per satu pakai curl/PowerShell sebelum sentuh kode client — supaya kalau ada error nanti, sudah pasti tahu itu bukan dari API.
Lebih lamaTerbaru

Posting Komentar