Laravel

Multi-tenancy Database-Native dengan PostgreSQL Row-Level Security di Laravel: Shift Arsitektur yang Sering Terlewatkan

Kholil · 17 Apr 2026 · 5 min read · 2 views
Multi-tenancy Database-Native dengan PostgreSQL Row-Level Security di Laravel: Shift Arsitektur yang Sering Terlewatkan

Menggali shift arsitektur tersembunyi di Laravel: bagaimana database-native multi-tenancy via PostgreSQL RLS mengubah paradigma keamanan SaaS.

Saat ini, ekosistem Laravel tengah ribut membicarakan Laravel Reverb untuk WebSocket dan NativePHP untuk aplikasi desktop. Namun, ada sesuatu yang jauh lebih signifikan terjadi di balik layar—terutama di kalangan enterprise yang membangun aplikasi dengan standar keamanan tinggi. Ini adalah tentang bagaimana menangani multi-tenancy bukan lagi di aplikasi, melainkan di tingkat database.

Mengapa Multi-tenancy Tradisional Adalah Celah Keamanan yang Tersembunyi

Hampir semua developer Laravel yang membangun aplikasi multi-tenant menggunakan pendekatan berbasis "Application Layer." Mereka mengandalkan package seperti spatie/laravel-multitenancy atau archtechx/tenancy, yang bekerja dengan Global Scopes di Eloquent. Setiap query secara otomatis mendapat tambahan klausa WHERE tenant_id = X.

Terdengar aman? Tidak sepenuhnya. Masalahnya adalah keamanan ini bergantung pada konsistensi developer. Jika ada satu model baru yang lupa mendapat trait multi-tenancy, atau ada raw SQL query yang ditulis tanpa filter tenant, maka Tenant A bisa melihat data Tenant B. Itu adalah bencana keamanan yang serius, terutama untuk aplikasi FinTech atau HealthTech.

"Keamanan berbasis konvensi adalah jaminan palsu. Keamanan sejati harus berada di infrastruktur, bukan di logika aplikasi."

Pengenalan PostgreSQL Row-Level Security (RLS)

PostgreSQL menawarkan fitur yang sering diabaikan: Row-Level Security (RLS). Ini adalah mekanisme yang memungkinkan database itu sendiri untuk menentukan baris mana yang bisa diakses oleh user tertentu, terlepas dari apa yang ditulis di aplikasi.

Dengan RLS, Anda bisa membuat policy di tingkat database yang mengatakan: "User ini hanya bisa melihat baris di mana tenant_id = nilai_yang_disimpan_di_session." Bahkan jika query Anda adalah SELECT * FROM orders; tanpa WHERE clause apapun, PostgreSQL akan secara otomatis memfilter hanya baris yang sesuai dengan policy yang Anda definisikan.

Cara Kerja: Arsitektur Database-Native Multi-tenancy

Mari kita breakdown bagaimana ini bekerja dalam praktik:

1. Middleware Laravel Mengidentifikasi Tenant

Request masuk ke aplikasi Laravel. Middleware Anda mengidentifikasi tenant (bisa dari subdomain, header, atau user session). Middleware kemudian menjalankan perintah ke database:

// app/Http/Middleware/SetTenantContext.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;

class SetTenantContext
{
    public function handle(Request $request, Closure $next)
    {
        $tenant = auth()->user()->tenant; // atau cara lain identifikasi tenant
        
        DB::statement(
            'SET app.current_tenant_id = ?',
            [$tenant->id]
        );
        
        return $next($request);
    }
}

2. Konfigurasi Row-Level Security di Database

Di PostgreSQL, Anda membuat RLS policy untuk setiap tabel yang perlu dilindungi:

-- Enable RLS pada tabel orders
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Buat policy: user hanya bisa melihat orders milik tenant mereka
CREATE POLICY orders_isolation ON orders
    FOR SELECT
    USING (tenant_id = current_setting('app.current_tenant_id')::bigint);

-- Sama untuk INSERT, UPDATE, DELETE
CREATE POLICY orders_insert ON orders
    FOR INSERT
    WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::bigint);

CREATE POLICY orders_update ON orders
    FOR UPDATE
    USING (tenant_id = current_setting('app.current_tenant_id')::bigint);

CREATE POLICY orders_delete ON orders
    FOR DELETE
    USING (tenant_id = current_setting('app.current_tenant_id')::bigint);

3. Laravel Menjadi "Tenant-Unaware"

Sekarang, model Eloquent Anda bisa sangat sederhana. Tidak perlu Global Scopes, tidak perlu trait khusus:

// app/Models/Order.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $fillable = ['tenant_id', 'customer_name', 'amount'];
    // Tidak ada trait, tidak ada scope khusus
}

Query di controller bisa sangat polos:

// Di controller
$orders = Order::all(); // PostgreSQL otomatis hanya return orders dari tenant aktif
$order = Order::find($id); // Aman! Jika ID bukan milik tenant, akan return null

Keuntungan Teknis yang Sesungguhnya

1. Defense in Depth (Pertahanan Berlapis): Bahkan jika ada bug di aplikasi atau developer baru yang tidak memahami multi-tenancy, database itu sendiri akan mencegah data leak.

2. Raw Queries Tetap Aman: Jika Anda terpaksa menulis raw SQL atau menggunakan stored procedure, keamanannya tetap terjaga oleh RLS policy di database.

3. Compliance dan Audit: Untuk industri regulated seperti FinTech atau HealthTech, keamanan di tingkat infrastruktur jauh lebih mudah di-audit dan di-verify daripada keamanan berbasis code.

4. Performance: RLS filtering terjadi di database engine, bukan di aplikasi. Untuk dataset besar, ini bisa lebih efisien karena filtering terjadi sebelum data dipindahkan ke aplikasi.

Tantangan dan Trade-off

Tidak semua orang memilih pendekatan ini, dan ada alasannya:

  • Kompleksitas Operasional: Memerlukan deep knowledge tentang PostgreSQL administration.
  • Debugging Lebih Susah: Ketika query "tidak mengembalikan hasil," Anda harus tahu bahwa mungkin itu karena RLS policy, bukan query logic yang salah.
  • Database Lock-in: Anda sekarang sangat terikat pada PostgreSQL. Migrasi ke database lain menjadi mustahil.
  • Testing Lebih Rumit: Unit test memerlukan setup RLS yang tepat untuk bisa berjalan dengan benar.

Mengapa Ini Belum Mainstream

Pendekatan ini "non-mainstream" karena beberapa alasan: 1. Berlawanan dengan "Laravel Way": Laravel selama ini mengajarkan bahwa logic aplikasi harus di dalam PHP code. RLS memindahkan bagian penting dari logic ke database, yang terasa seperti "stepping outside the framework." 2. Skill Gap: Tidak semua Laravel developer adalah DBA yang baik. Memahami RLS, session variables, dan PostgreSQL configuration memerlukan pembelajaran tambahan yang signifikan. 3. False Sense of Security sebelumnya: Banyak startup yang tumbuh dengan multi-tenancy berbasis application-layer dan tidak pernah mengalami breaches serius, jadi mereka tidak merasa perlu untuk mengupgrade ke pendekatan yang lebih aman.

Kapan Anda Harus Pertimbangkan RLS

Pendekatan ini paling cocok jika:

  • Anda membangun aplikasi FinTech, HealthTech, atau SaaS dengan compliance tinggi.
  • Anda memiliki developer team yang besar di mana Anda tidak bisa menjamin bahwa semua orang akan selalu mengikuti convention.
  • Anda sudah menggunakan PostgreSQL sebagai database utama.
  • Anda tidak berencana untuk migrasi database di masa depan.
  • Anda memiliki resources dan expertise untuk maintain infrastruktur yang lebih kompleks.

Kesimpulan: Security by Infrastructure, Bukan Convention

Laravel telah mengajarkan kita bahwa "Convention over Configuration" adalah cara yang benar. Namun, ketika datang ke keamanan data dalam aplikasi multi-tenant yang menangani informasi sensitif, convention saja tidak cukup. Security harus menjadi bagian dari infrastruktur, bukan hanya dari code yang ditulis developer.

PostgreSQL Row-Level Security adalah frontier baru dalam arsitektur Laravel yang aman. Mungkin itu tidak akan pernah menjadi "mainstream" di kalangan developer yang membangun todo apps atau blog sederhana. Tetapi untuk mereka yang serius membangun SaaS yang "bulletproof," ini adalah shift arsitektur yang perlu diperhatikan—bukan hari ini, mungkin, tetapi pasti di masa depan.

Pertanyaannya bukan lagi apakah Anda perlu RLS, tetapi kapan Anda akan merasa perlu untuk mengimplementasikannya.