Membuat REST API dengan Laravel Sanctum

Aplikasi Laravel Anda sudah berjalan di browser, lalu datang permintaan baru: tim mobile butuh endpoint untuk login dan mengambil data produk. Session dan cookie bawaan tidak cocok...

Membuat REST API dengan Laravel Sanctum

Aplikasi Laravel Anda sudah berjalan di browser, lalu datang permintaan baru: tim mobile butuh endpoint untuk login dan mengambil data produk. Session dan cookie bawaan tidak cocok untuk aplikasi Android atau iOS, sementara OAuth2 penuh terasa berlebihan untuk kebutuhan sesederhana itu. Di sinilah Laravel Sanctum berguna.

Tutorial ini membangun REST API di Laravel 11 dari awal: register, login, logout, route terproteksi, pembatasan hak token, rate limiting, dan format JSON yang konsisten. Di setiap langkah ada penjelasan kenapa langkah itu perlu, bukan hanya kodenya.

Kapan Memakai Sanctum, Kapan Tidak

Sanctum punya dua mode. Mode API token menerbitkan token acak yang disimpan di tabel personal_access_tokens, cocok untuk aplikasi mobile dan integrasi server. Mode SPA authentication memakai cookie session untuk frontend React/Vue yang berada di domain yang sama. Artikel ini fokus pada mode token.

KebutuhanPilihan yang cocokAlasan
Aplikasi mobile milik sendiriSanctum (token)Ringan, token bisa dicabut per perangkat
SPA di domain yang samaSanctum (cookie)Terlindungi CSRF, token tidak disimpan di JavaScript
Pihak ketiga login atas nama user AndaPassport (OAuth2)Butuh alur authorization code dan client terdaftar
Komunikasi antar-server internalSanctum token dengan abilities terbatasCukup satu token per layanan

Jika Anda masih menimbang bentuk API-nya, baca juga perbandingan REST API vs GraphQL sebelum mulai.

Langkah 1: Instalasi

Di Laravel 11, file routes/api.php tidak lagi ada secara default. Satu perintah menyiapkan semuanya:

php artisan install:api
php artisan migrate

Perintah pertama memasang laravel/sanctum, membuat routes/api.php, mendaftarkannya di bootstrap/app.php, dan menerbitkan migration tabel token. Pastikan koneksi database di .env sudah benar sebelum migrate; panduan konfigurasi .env database membahasnya.

Tambahkan trait HasApiTokens ke model User. Langkah ini sering terlupa karena tidak dilakukan otomatis:

use Laravel\Sanctum\HasApiTokens;

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

Langkah 2: Register, Login, Logout

php artisan make:controller Api/AuthController
namespace App\Http\Controllers\Api;

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

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $data = $request->validate([
            'name'        => 'required|string|max:255',
            'email'       => 'required|email|unique:users',
            'password'    => 'required|min:8|confirmed',
            'device_name' => 'required|string|max:100',
        ]);

        $user = User::create([
            'name'     => $data['name'],
            'email'    => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $token = $user->createToken($data['device_name'], ['product:read'])->plainTextToken;

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

    public function login(Request $request)
    {
        $data = $request->validate([
            'email'       => 'required|email',
            'password'    => 'required',
            'device_name' => 'required|string|max:100',
        ]);

        $user = User::where('email', $data['email'])->first();

        if (! $user || ! Hash::check($data['password'], $user->password)) {
            return response()->json(['message' => 'Kredensial tidak valid.'], 401);
        }

        $abilities = $user->is_admin ? ['*'] : ['product:read'];
        $token = $user->createToken($data['device_name'], $abilities, now()->addDays(30));

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

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

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

Beberapa keputusan di kode ini patut dijelaskan:

  • Pesan error login sama untuk email tidak terdaftar maupun password salah, supaya penyerang tidak bisa menebak email mana yang valid.
  • device_name menjadi nama token. Saat user kehilangan HP, Anda cukup menghapus token bernama "HP Samsung" tanpa memaksa logout di perangkat lain.
  • Parameter ketiga createToken() menetapkan tanggal kedaluwarsa per token. Kolom is_admin di contoh hanyalah asumsi; sesuaikan dengan cara aplikasi Anda menandai admin.
  • Token disimpan sebagai hash SHA-256 di database. Teks aslinya hanya muncul sekali di respons, jadi klien wajib menyimpannya.

Langkah 3: Route dan Proteksi

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

Route::post('/register', [AuthController::class, 'register'])->middleware('throttle:5,1');
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:5,1');

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn (Request $request) => $request->user());
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/products', [ProductController::class, 'index']);
    Route::post('/products', [ProductController::class, 'store'])->middleware('abilities:product:write');
});

throttle:5,1 membatasi lima percobaan per menit per IP, cukup untuk manusia tetapi menyulitkan brute force. Middleware abilities perlu didaftarkan dulu di bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
        'ability'   => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
    ]);
})

Di dalam controller Anda juga bisa memeriksa secara manual dengan $request->user()->tokenCan('product:write').

Langkah 4: JSON Rapi dengan API Resource

Mengembalikan model mentah berarti setiap kolom baru di tabel ikut terkirim ke klien, termasuk yang tidak seharusnya. API Resource memisahkan struktur database dari kontrak API:

php artisan make:resource ProductResource
public function toArray(Request $request): array
{
    return [
        'id'         => $this->id,
        'name'       => $this->name,
        'price'      => (int) $this->price,
        'created_at' => $this->created_at->toDateString(),
    ];
}
public function index()
{
    return ProductResource::collection(Product::latest()->paginate(10));
}

Koleksi berpaginasi otomatis membawa blok links dan meta, sehingga aplikasi mobile tahu ada halaman berikutnya atau tidak. Untuk endpoint daftar yang memuat relasi, pakai eager loading agar tidak terjadi N+1 query; detailnya ada di artikel optimasi query Eloquent.

Langkah 5: Error Selalu dalam JSON

Klien yang lupa mengirim Accept: application/json akan menerima halaman HTML atau redirect ke /login saat token salah. Paksa semua route api/* merespons JSON di bootstrap/app.php:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->shouldRenderJsonWhen(
        fn (Request $request, Throwable $e) => $request->is('api/*') || $request->expectsJson()
    );
})

Langkah 6: Uji dengan cURL

curl -X POST http://localhost:8000/api/login \
  -H "Accept: application/json" \
  -d "email=budi@mail.com&password=rahasia88&device_name=postman"

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

Di Postman, pilih tab Authorization, tipe Bearer Token, lalu tempel token tersebut.

Langkah 7: Kunci Perilaku dengan Feature Test

Mengetes API secara manual lewat Postman setiap kali ada perubahan cepat membosankan, dan akhirnya dilewati. Feature test menjalankan skenario yang sama dalam hitungan detik. Helper Sanctum::actingAs() membuat user terautentikasi dengan abilities tertentu tanpa perlu login sungguhan:

use App\Models\User;
use Laravel\Sanctum\Sanctum;

public function test_tamu_tidak_bisa_melihat_produk(): void
{
    $this->getJson('/api/products')->assertUnauthorized();
}

public function test_token_read_tidak_bisa_menambah_produk(): void
{
    Sanctum::actingAs(User::factory()->create(), ['product:read']);

    $this->postJson('/api/products', ['name' => 'Kopi'])->assertForbidden();
}

Dua test ini menjaga hal yang paling mahal jika rusak: route yang tiba-tiba terbuka tanpa token, dan token biasa yang bisa menulis data.

Merencanakan Versi API

Begitu aplikasi mobile terpasang di ribuan HP, Anda tidak bisa memaksa semua orang memperbarui aplikasi di hari yang sama. Karena itu, beri prefix versi sejak awal, misalnya Route::prefix('v1'). Saat format respons harus berubah drastis, buat v2 dan biarkan v1 hidup beberapa bulan sambil memantau pemakaiannya di log. Perubahan kecil seperti menambah field baru tidak memerlukan versi baru karena klien lama cukup mengabaikannya.

Error Umum dan Solusinya

GejalaPenyebabPerbaikan
Call to undefined method createToken()Trait HasApiTokens belum dipasangTambahkan use HasApiTokens di model User
Error "Route [login] not defined"Request tanpa header Accept dialihkan ke halaman loginKirim header Accept atau pakai shouldRenderJsonWhen
404 pada /api/...install:api belum dijalankanJalankan perintahnya, lalu cek php artisan route:list
Selalu 401 padahal token benarHeader Authorization dibuang oleh server (umum di Apache/CGI)Pastikan .htaccess bawaan Laravel di folder public terpasang
Tabel token membengkakToken kedaluwarsa tidak pernah dihapusJadwalkan sanctum:prune-expired

Untuk poin terakhir, tambahkan jadwal di routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();

Checklist Sebelum API Dipakai Produksi

  • Seluruh trafik lewat HTTPS, tanpa pengecualian.
  • Endpoint login dan register diberi throttle.
  • Token diberi nama perangkat dan masa berlaku.
  • Abilities dibatasi seminimal mungkin per peran.
  • Semua respons memakai API Resource, bukan model mentah.
  • Error 401, 403, 404, dan 422 selalu berbentuk JSON.
  • Ada feature test untuk login gagal, login sukses, dan akses tanpa token.

Daftar praktik keamanan yang lebih luas ada di 10 best practice keamanan aplikasi web. Dengan pola di atas, API Anda tidak hanya berjalan, tetapi juga mudah dirawat ketika jumlah klien dan endpoint bertambah.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Laravel Sanctum Api Programming & Laravel
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →