Kamu baru me-deploy REST API Laravel kemarin. Hari ini log server sudah penuh dengan percobaan brute force ke /api/login. Atau lebih parah: endpoint GET /api/posts/5 ternyata bisa diakses user lain hanya dengan mengganti angka di URL. Dua skenario ini lebih sering terjadi daripada yang dibayangkan.
REST API dirancang agar stateless dan terekspos ke publik — siapa saja yang tahu URL-nya bisa mengirim request. Tanpa proteksi yang memadai, API menjadi target empuk untuk serangan. Masalahnya, banyak developer terlalu fokus mengejar fitur baru hingga lupa bahwa keamanan bukan fitur tambahan, melainkan requirement.
Artikel ini membahas langkah-langkah praktis mengamankan REST API di Laravel, mengacu pada standar OWASP API Security Top 10 (2023). Semua contoh kode menggunakan Laravel 11.x dengan Laravel Sanctum. Jika kamu belum familiar dengan konsep REST API secara umum, bisa membaca panduan REST API kami terlebih dahulu.
REST API Perlu Keamanan Khusus
REST API bersifat stateless dan terekspos publik via HTTP. Setiap endpoint adalah potensi titik masuk serangan. Tanpa authentication yang memadai, siapa saja dapat memanggil endpoint sensitif. Tanpa authorization, user yang sudah login dapat mengakses atau mengubah data milik user lain (BOLA). Tanpa rate limiting, endpoint dapat di-spam atau di-brute-force tanpa batas. Tanpa validasi input, aplikasi rentan terhadap SQL injection dan mass assignment.
Berikut beberapa risiko dari OWASP API Security Top 10 (2023) yang paling relevan bagi kamu sebagai developer Laravel:
Broken Object Level Authorization (BOLA) — API1
Ini risiko nomor satu. User terautentikasi mengakses data milik user lain hanya dengan mengubah ID di URL. Mitigasinya adalah mengecek kepemilikan data pada setiap resource, bukan hanya mengecek status login. Laravel menyediakan Policy dan Gate untuk hal ini.
Broken Authentication — API2
Authentication yang lemah atau token tanpa expiration membuat akun rentang dicoba-coba. Sanctum menghasilkan token yang di-hash SHA-256 sebelum disimpan di database, dan kamu bisa mengatur expiration per token agar token bocor tidak usable selamanya.
Broken Object Property Level Authorization — API3
Gabungan mass assignment dan excessive data exposure. Attacker mengirim field tambahan seperti is_admin: true di body request. Perlindungannya: $fillable di model dan $request->validated() di controller, serta API Resource untuk membatasi field yang dikembalikan.
Unrestricted Resource Consumption — API4
Tanpa rate limiting, penyerang bisa melakukan brute force login atau DDoS layer 7. Laravel punya RateLimiter dan middleware throttle untuk membatasi jumlah request per user atau per IP.
Broken Function Level Authorization — API5
User dengan role biasa mencoba mengakses endpoint admin. Perlindungannya: middleware role dan pemeriksaan can() di controller atau Form Request.
Security Misconfiguration — API8
APP_DEBUG=true di production, CORS ['*'], dan stack trace yang keluar ke client adalah contoh konfigurasi salah yang membocorkan informasi sensitif.
Untuk memahami perbedaan authentication dan authorization secara mendalam, baca artikel perbedaan keduanya, serta pengertian authentication dan pengertian autorisasi. Jika ingin tahu lebih tentang SQL injection, ada panduan pencegahannya.
Authentication: Memverifikasi Identitas Pemanggil API
Konsep Authentication dan Bearer Token
Authentication memverifikasi siapa pemanggil API. Karena REST API stateless, setiap request harus membawa konteks otentikasi sendiri, biasanya melalui Bearer Token di header Authorization: Bearer <token> (menurut RFC 6750). Token tidak disimpan di session server, sehingga API bisa diskalakan secara horizontal.
Untuk mempelajari konsep authentication lebih lanjut, baca pengertian authentication. Jika mempertimbangkan JWT sebagai alternatif token stateless, ada panduan JWT.
Setup Laravel Sanctum
Di Laravel 11, pemasangan Sanctum cukup satu perintah:
php artisan install:api
Perintah ini meng-install Sanctum, membuat migration untuk tabel personal_access_tokens, dan mengonfigurasi route API.
Tambahkan trait HasApiTokens pada model User:
<?php
namespace App\Models;
use Laravel\Sanctum\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens;
// ...
}
Token yang dibuat Sanctum di-hash dengan SHA-256 sebelum disimpan ke database. Nilai plain-text token hanya ditampilkan sekali saat dibuat, jadi pastikan kamu menyimpannya ke client segera.
Endpoint Login: POST /api/login
Berikut contoh endpoint login lengkap yang memverifikasi kredensial dan menerbitkan token Sanctum:
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (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' => ['Kredensial yang dimasukkan tidak valid.'],
]);
}
$token = $user->createToken('api-token')->plainTextToken;
return response()->json([
'message' => 'Login berhasil.',
'token' => $token,
'token_type' => 'Bearer',
]);
});
Client yang menerima plainTextToken harus menyimpannya dan mengirimkannya sebagai header Authorization: Bearer <token> pada setiap request berikutnya. Untuk verifikasi password secara aman, kamu bisa merujuk ke panduan menyimpan password dengan aman.
Endpoint login adalah target utama brute force, jangan lupa pasang rate limiting yang ketat (misal 5 percobaan per menit per IP) — akan dibahas di bagian rate limiting.
Proteksi Route dengan auth:sanctum
Gunakan middleware auth:sanctum untuk melindungi route yang membutuhkan authentication:
use App\Http\Controllers\Api\PostController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);
});
Setiap request ke route dalam group ini tanpa token yang valid akan mendapat response 401 Unauthorized. Route di luar group, seperti /api/login, tetap bisa diakses publik.
Token Abilities (Scopes)
Tidak semua token perlu akses ke semua endpoint. Sanctum mendukung token abilities — semacam scope — yang membatasi apa yang bisa dilakukan sebuah token:
$token = $user->createToken('mobile-app', ['posts:read', 'posts:write']);
Di controller atau policy, cek ability dengan tokenCan():
if ($request->user()->tokenCan('posts:write')) {
// token memiliki izin menulis post
}
Kamu juga bisa menggunakan middleware abilities di route untuk membatasi akses berdasarkan ability tertentu. Fitur ini berguna saat satu user punya beberapa token untuk keperluan berbeda, misalnya token read-only untuk integrasi pihak ketiga dan token full-access untuk aplikasi utama. Untuk memahami hubungan antara ability dan authorization, baca pengertian autorisasi.
Token Expiration dan Revocation
Token tanpa expiration adalah risiko. Kalau token bocor, penyerang bisa menggunakannya tanpa batas waktu. Ada dua cara mengatur expiration di Sanctum.
Secara global di config/sanctum.php, atur kunci expiration dalam menit:
'expiration' => 60 * 24 * 7, // 7 hari
Atau per token saat pembuatan, dengan parameter ketiga:
$user->createToken('api-token', ['*'], now()->addWeek());
Untuk mencabut token, misalnya saat logout:
// Cabut token yang sedang digunakan
$request->user()->currentAccessToken()->delete();
// Cabut semua token milik user
$user->tokens()->delete();
Jangan lupa jadwalkan pembersihan token expired agar tabel personal_access_tokens tidak menumbuhi tak terbatas. Di Laravel 11, masukkan method ini ke dalam rantai Application::configure(...) di bootstrap/app.php:
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Application;
return Application::configure(basePath: dirname(__DIR__))
->withSchedule(function (Schedule $schedule) {
$schedule->command('sanctum:prune-expired --hours=24')->daily();
})
->create();
Authorization: Mengecek Hak Akses per Resource
Authentication menjawab "siapa" yang mengakses. Authorization menjawab "boleh atau tidak" melakukan aksi pada resource tertentu. Banyak developer berhenti di authentication saja, padahal itu cukup berbahaya.
Masalah Broken Object Level Authorization (BOLA)
OWASP menempatkan BOLA sebagai risiko nomor satu di API Security Top 10 (2023). Skenarionya sederhana: user A login, kemudian mengakses GET /api/posts/5, ternyata post itu milik user B. Kalau endpoint tidak mengecek kepemilikan data, user A bisa membaca — bahkan mengubah — data milik orang lain.
Contoh controller yang rentan:
// BERBAHAYA: tidak ada pengecekan kepemilikan data
public function show(int $id)
{
return Post::findOrFail($id);
}
User manapun yang sudah login bisa mengakses post siapa saja dengan menebak atau mengganti ID di URL.
Solusinya, cek kepemilikan sebelum mengembalikan data:
public function show(Post $post)
{
if ($post->user_id !== auth()->id()) {
abort(403, 'Anda tidak memiliki akses ke resource ini.');
}
return new PostResource($post);
}
Pengecekan seperti ini mencegah user mengakses resource milik user lain hanya dengan manipulasi ID di URL. Untuk pemahaman lebih lanjut tentang autorisasi, baca pengertian autorisasi dan perbedaan authentication vs authorization.
Implementasi Policy dengan PostPolicy
Untuk authorization yang terstruktur, Laravel menyediakan Policy — class per model. Berikut contoh PostPolicy:
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
public function view(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->is_admin;
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->is_admin;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->is_admin;
}
}
Laravel 11 otomatis menemukan policy dengan nama yang sesuai konvensi (Post model ↔ PostPolicy). Kemudian panggil di controller dengan $this->authorize():
public function show(Post $post)
{
$this->authorize('view', $post);
return new PostResource($post);
}
public function update(UpdatePostRequest $request, Post $post)
{
$this->authorize('update', $post);
$post->update($request->validated());
return new PostResource($post);
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return response()->noContent();
}
Dengan policy, setiap akses ke resource dicek kepemilikannya. User yang mencoba mengakses resource milik orang lain akan mendapat response 403 Forbidden. Admin yang punya flag is_admin tetap bisa mengakses semua resource melalui logika tambahan di method policy.
Rate Limiting: Mencegah Abuse dan Brute Force
Tanpa rate limiting, endpoint API bisa di-hit ribuan kali per detik. Penyerang bisa brute force endpoint login, scrape data massal, atau melakukan DDoS layer 7. OWASP mengategorikan ini sebagai API4 — Unrestricted Resource Consumption.
Mendefinisikan Rate Limiter
Di AppServiceProvider::boot(), definisikan rate limiter bernama api:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(
$request->user()?->id ?: $request->ip()
);
});
}
Rate limiter ini membatasi 60 request per menit. Untuk user yang sudah login, limit dihitung per user ID. Untuk guest, dihitung per IP address.
Untuk endpoint sensitif seperti login, gunakan limit lebih ketat:
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(5)->by('login:minute:'.$request->ip()),
Limit::perDay(50)->by('login:day:'.$request->ip()),
];
});
Dua limit sekaligus: maksimal 5 percobaan per menit dan 50 per hari dari satu IP. Prefix by yang unik pada setiap limit penting saat mengembalikan array limits, agar Laravel melacaknya dengan benar. Ini efektif mencegah brute force yang pelan tapi konsisten.
Memasang di Route
Attach rate limiter via middleware throttle:
Route::post('/login', [AuthController::class, 'login'])
->middleware('throttle:login');
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
});
Saat limit tercapai, Laravel otomatis mengembalikan response 429 Too Many Requests dengan header Retry-After.
Untuk production dengan traffic tinggi dan banyak server, pertimbangkan Redis sebagai backend throttle. Di Laravel 11, masukkan method ini ke dalam rantai Application::configure(...) di bootstrap/app.php:
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Foundation\Application;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->throttleWithRedis();
})
->create();
Redis lebih akurat untuk rate limiting di environment multi-server dibanding file atau database cache driver. Untuk panduan lengkap rate limiting, baca pengertian rate limiting dan panduan rate limiting Laravel.
Validasi Input dan Proteksi Mass Assignment
API menerima data dari luar. Data dari luar tidak pernah boleh dipercaya — apapun yang dikirim client harus divalidasi sebelum diproses.
Form Request Validation
Pisahkan logika validasi dari controller menggunakan Form Request:
php artisan make:request StorePostRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // authorization ditangani policy
}
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'body' => 'required|string|max:10000',
'status' => 'sometimes|in:draft,published',
];
}
}
Di controller, gunakan $request->validated() — bukan $request->all():
public function store(StorePostRequest $request)
{
$post = $request->user()->posts()->create($request->validated());
return new PostResource($post);
}
Perbedaannya kritis: $request->all() mengembalikan semua data yang dikirim client, termasuk field yang tidak divalidasi. $request->validated() hanya mengembalikan field yang sudah lolos validasi. Ini adalah pertahanan pertama terhadap mass assignment.
Pencegahan SQL Injection
Eloquent ORM dan Query Builder Laravel otomatis menggunakan prepared statements / parameter binding, sehingga query seperti berikut aman:
// Aman — parameter binding otomatis
$user = User::where('email', $request->email)->first();
$posts = DB::table('posts')->where('user_id', $userId)->get();
Yang berbahaya adalah raw query tanpa binding:
// BERBAHAYA — rentan SQL injection
$results = DB::select("SELECT * FROM users WHERE id = $id");
// AMAN — gunakan parameter binding
$results = DB::select("SELECT * FROM users WHERE id = ?", [$id]);
Hal yang sama berlaku untuk whereRaw() dan orderByRaw() — selalu bind parameter, jangan pernah interpolasi variabel langsung ke dalam string query. Untuk detail SQL injection, baca panduan pencegahan SQL injection.
Mass Assignment Protection
OWASP API3 — Broken Object Property Level Authorization. Serangan mass assignment terjadi ketika attacker mengirim field tambahan yang tidak seharusnya tersedia:
{
"title": "Post Biasa",
"body": "Konten...",
"is_admin": true
}
Kalau model tidak memiliki proteksi $fillable dan controller menggunakan $request->all(), field is_admin ikut tersimpan ke database.
Proteksi di level model:
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'password',
];
// is_admin TIDAK ada di $fillable
}
Kombinasi $fillable di model dan $request->validated() di controller memberikan proteksi ganda. Field yang tidak ada di validation rules tidak akan masuk ke validated(), dan field yang tidak ada di $fillable tidak bisa di-mass assign ke model.
Hindari $guarded = [] tanpa alasan yang sangat kuat. Ini membuka semua field untuk mass assignment. Untuk detail verifikasi password yang terkait dengan model User, baca panduan password hashing.
Membatasi Data yang Dikembalikan dengan API Resource
Mengembalikan Eloquent model mentah risiko membocorkan kolom sensitif, misalnya password hash atau internal ID. Eloquent API Resource (JsonResource) berfungsi sebagai lapisan transformasi yang mengontrol field apa saja yang sampai ke client:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'status' => $this->status,
'created_at' => $this->created_at->toIso8601String(),
];
}
}
Dengan resource, hanya field yang kamu sediakan secara eksplisit yang dikembalikan. Field lain di database, termasuk data sensitif, tidak akan bocor ke response API. Ini menjadi lapisan pertahanan tambahan di sisi authorization dan data exposure.
CORS, HTTPS, dan Error Handling
Tiga aspek ini sering dianggap sepele, tapi bisa menjadi celah keamanan serius kalau salah konfigurasi.
Konfigurasi CORS yang Aman
CORS (Cross-Origin Resource Sharing) mengontrol domain mana yang boleh mengakses API dari browser. Publikasikan konfigurasi CORS:
php artisan config:publish cors
Di config/cors.php, atur allowed_origins secara spesifik ke domain frontend:
'allowed_origins' => [
'https://app.example.com',
'https://admin.example.com',
],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'allowed_headers' => ['Authorization', 'Content-Type', 'X-Requested-With'],
'supports_credentials' => true, // diperlukan jika menggunakan Sanctum SPA auth
Jangan pernah gunakan 'allowed_origins' => ['*'] di production. Wildcard mengizinkan domain manapun — termasuk domain milik penyerang — mengirim request ke API.
Header respons yang relevan meliputi Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, dan Access-Control-Allow-Headers. Untuk detail CORS, baca panduan CORS lengkap atau pengertian CORS. Jika menggunakan Sanctum SPA auth berbasis cookie, ada juga hubungan antara CORS dan CSRF yang dibahas di panduan CSRF.
HTTPS dan Transport Security
REST API wajib diakses via HTTPS di production. Tanpa HTTPS, semua data — termasuk token authentication — dikirim dalam plaintext dan bisa diintercept oleh siapa saja di jaringan yang sama.
Pastikan APP_URL menggunakan HTTPS:
APP_URL=https://api.example.com
Force HTTPS scheme di AppServiceProvider:
use Illuminate\Support\Facades\URL;
public function boot(): void
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
Tambahkan header HSTS (HTTP Strict Transport Security) di web server atau middleware untuk memaksa browser selalu menggunakan HTTPS. Hindari mixed content; semua asset dan callback harus pakai HTTPS.
Error Handling yang Aman (OWASP API8)
APP_DEBUG=true di production adalah salah satu kesalahan paling berbahaya. OWASP mengategorikan ini sebagai API8 — Security Misconfiguration. Saat debug mode aktif, error response bisa menampilkan stack trace lengkap, path file di server, database credentials dari environment variables, dan query SQL yang gagal.
Pastikan di production:
APP_DEBUG=false
APP_ENV=production
Untuk API, kembalikan error response yang konsisten tanpa informasi internal. Di Laravel 11, masukkan method ini ke dalam rantai Application::configure(...) di bootstrap/app.php:
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
return Application::configure(basePath: dirname(__DIR__))
->withExceptions(function ($exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Resource tidak ditemukan.',
], 404);
}
});
})
->create();
Prinsipnya: log detail error di server (Laravel Log), kembalikan pesan generik ke client. Developer butuh stack trace untuk debugging — penyerang juga butuh stack trace untuk menemukan celah. Format respons error yang konsisten, misal { "message": "...", "errors": {...} }, membuat frontend lebih mudah menangani kesalahan tanpa reveal informasi internal.
Security Checklist
Berikut ringkasan langkah-langkah keamanan REST API Laravel yang sudah dibahas, bisa kamu jadikan referensi cepat sebelum deploy ke production:
- Authentication — Pasang middleware
auth:sanctumpada semua endpoint yang membutuhkan autentikasi. - Authorization — Implementasikan Policy untuk setiap resource; cek kepemilikan data, bukan hanya status login.
- Token Expiration — Set expiration pada token Sanctum dan jadwalkan
sanctum:prune-expiredharian. - Rate Limiting — Definisikan rate limiter, pasang
throttlemiddleware, dan gunakan limit ketat untuk endpoint login. - Input Validation — Gunakan Form Request dan selalu pakai
$request->validated(). - Mass Assignment — Set
$fillabledi setiap model; hindari$guarded = []tanpa alasan kuat. - SQL Injection — Gunakan Eloquent/Query Builder dan bind parameter di raw query.
- CORS — Set
allowed_originsspesifik; jangan gunakan['*']di production. - HTTPS — Wajib HTTPS di production; aktifkan HSTS header.
- Error Handling —
APP_DEBUG=falsedi production; jangan expose stack trace ke client. - API Resource — Gunakan Eloquent API Resource untuk mengontrol field yang dikembalikan; hindari mengembalikan data sensitif seperti password hash.
- Logging — Log semua authentication failure dan aktivitas mencurigakan.
Penutup
Mengamankan REST API bukan pekerjaan sekali jadi. Ancaman berkembang, dan konfigurasi yang aman hari ini bisa menjadi celah besok kalau tidak di-maintain. Tapi langkah-langkah di atas — authentication dengan Sanctum, authorization per resource, rate limiting, validasi input, konfigurasi CORS, HTTPS, dan error handling — sudah mencakup mayoritas vektor serangan yang paling umum.
Mulai dari yang paling kritis: pastikan authentication dan authorization sudah benar. Endpoint yang tidak mengecek kepemilikan data (BOLA) adalah celah yang paling sering ditemukan dan paling mudah dieksploitasi. Jangan biarkan API baru kamu menjadi korban brute force atau bocor data hanya karena langkah-langkah dasar ini diabaikan.
Untuk memahami konsep keamanan API secara lebih mendalam, baca OWASP API Security Top 10 (2023) — dokumen gratis yang menjadi acuan standar industri untuk keamanan API. Referensi resmi lainnya adalah dokumentasi Laravel Sanctum untuk implementasi authentication token di Laravel 11.x.
Fitur komentar belum diaktifkan oleh administrator.