Sistem backend modern sering membagi evaluasi otorisasi ke dalam beberapa lapisan: API Gateway menangani autentikasi awal, middleware memvalidasi token dan konteks tenant, lalu internal service mengevaluasi izin granular berbasis resource. Secara terisolasi, logika pada tiap layer tampak aman (analisis). Namun, ketika aturan-aturan ini beroperasi bersamaan (sintesis), celah privilege escalation atau token bypass kerap muncul akibat ambiguitas parsing, header injection, atau asumsi status token yang inkonsisten.

Problem: Sintesis Lebih Sulit daripada Analisis

Menganalisis keamanan sebuah komponen individual relatif mudah: fungsi validasi JWT hanya memeriksa masa berlaku dan signature. Masalah muncul saat beberapa sistem independen digabungkan. Jika Gateway memvalidasi identitas lalu meneruskan header X-User-Role: admin ke internal service melalui plain HTTP tanpa enkripsi atau re-signing konteks, klien yang berhasil membobol routing Gateway atau memalsukan header internal dapat melakukan eskalasi hak akses secara langsung.

Prinsip dasarnya: keamanan sistem tidak sama dengan jumlah keamanan komponen individualnya. Ketika dependensi implisit antar-layer terlewat, celah komposisi tercipta.

Arsitektur Validasi: Gateway Enforcement vs. Service Context

Untuk mencegah celah antar-layer tanpa mengorbankan performa, gunakan pola verifikasi sentral di boundary layer dan penegakan konteks yang immutable di internal service:

  • Edge Gateway: Memeriksa validitas kriptografis JWT, membuang semua header internal (seperti X-Internal-*) yang dikirim oleh client, lalu mengekstrak klaim identitas ke dalam bentuk token kontekstual bertanda tangan (HMAC token internal atau mTLS context).
  • Internal Middleware: Mengonsumsi konteks identitas yang telah diverifikasi tanpa melakukan panggilan verifikasi JWT eksternal yang redundan. Middleware mengonversi payload terpercaya menjadi context terstruktur di memori proses.
  • Domain Handler: Hanya mengeksekusi aturan bisnis spesifik resource berdasarkan data context yang telah dibersihkan.

Implementasi Middleware Auth di Go

Kode berikut menerapkan middleware Go yang bertindak sebagai boundary aman. Middleware membersihkan input liar, memvalidasi klaim identitas terpercaya, dan menyuntikkannya ke dalam context.Context standar tanpa alokasi memori berlebih.

package auth

import (
	"context"
	"crypto/subtle"
	"errors"
	"net/http"
)

type contextKey string

const (
	UserContextKey contextKey = "auth_user"
	RoleContextKey contextKey = "auth_role"
)

var (
	ErrUnauthorized = errors.New("unauthorized: missing or invalid credentials")
	ErrForbidden    = errors.New("forbidden: insufficient permissions")
)

type PolicyEnforcer struct {
	internalSecret []byte
}

func NewPolicyEnforcer(secret string) *PolicyEnforcer {
	return &PolicyEnforcer{
		internalSecret: []byte(secret),
	}
}

// Middleware menyaring header publik dan memverifikasi sign internal gateway.
func (pe *PolicyEnforcer) Middleware(requiredRole string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			tokenSignature := r.Header.Get("X-Internal-Signature")
			user := r.Header.Get("X-User-ID")
			role := r.Header.Get("X-User-Role")

			// Cegah bypass: tolak permintaan tanpa penanda integritas internal
			if subtle.ConstantTimeCompare([]byte(tokenSignature), pe.internalSecret) != 1 {
				http.Error(w, ErrUnauthorized.Error(), http.StatusUnauthorized)
				return
			}

			// ponytail: simpan roles dalam context byte; upgrade ke bitmask jika enum membesar
			if requiredRole != "" && role != requiredRole {
				http.Error(w, ErrForbidden.Error(), http.StatusForbidden)
				return
			}

			ctx := context.WithValue(r.Context(), UserContextKey, user)
			ctx = context.WithValue(ctx, RoleContextKey, role)

			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

Verifikasi Sintesis Kebijakan Auth dengan Table-Driven Test

Verifikasi komposisi aturan membutuhkan pengujian kombinasi skenario tepi, termasuk kasus header injection dari client yang mencoba melewati boundary Gateway.

package auth_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"auth"
)

func TestPolicySynthesis(t *testing.T) {
	const secret = "internal-shared-secret-key"
	enforcer := auth.NewPolicyEnforcer(secret)

	dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("success"))
	})

	tests := []struct {
		name           string
		requiredRole   string
		headers        map[string]string
		expectedStatus int
	}{
		{
			name:         "Bypass injection attempt: Role admin tanpa internal signature",
			requiredRole: "admin",
			headers: map[string]string{
				"X-User-ID":   "attacker",
				"X-User-Role": "admin",
			},
			expectedStatus: http.StatusUnauthorized,
		},
		{
			name:         "Mismatch role: Signature sah tetapi role tidak mencukupi",
			requiredRole: "admin",
			headers: map[string]string{
				"X-Internal-Signature": secret,
				"X-User-ID":             "guest_user",
				"X-User-Role":           "member",
			},
			expectedStatus: http.StatusForbidden,
		},
		{
			name:         "Valid composition: Signature sah dan role sesuai",
			requiredRole: "admin",
			headers: map[string]string{
				"X-Internal-Signature": secret,
				"X-User-ID":             "root_admin",
				"X-User-Role":           "admin",
			},
			expectedStatus: http.StatusOK,
		},
		{
			name:         "Tampered signature attempt: Nilai rahasia salah",
			requiredRole: "member",
			headers: map[string]string{
				"X-Internal-Signature": "invalid-secret",
				"X-User-ID":             "victim",
				"X-User-Role":           "member",
			},
			expectedStatus: http.StatusUnauthorized,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/protected", nil)
			for k, v := range tc.headers {
				req.Header.Set(k, v)
			}

			rec := httptest.NewRecorder()
			handler := enforcer.Middleware(tc.requiredRole)(dummyHandler)
			handler.ServeHTTP(rec, req)

			if rec.Code != tc.expectedStatus {
				t.Fatalf("expected status %d, got %d", tc.expectedStatus, rec.Code)
			}
		})
	}
}

Praktik Hardening Komposisi Layer

  • Strip Headers di Edge: Konfigurasikan ingress proxy (Envoy, NGINX, atau Traefik) untuk secara eksplisit menghapus semua header X-Internal-* atau X-User-* dari request yang berasal dari publik sebelum sampai ke kode aplikasi.
  • Gunakan Explicit Trust Boundary: Gunakan mutual TLS (mTLS) pada service mesh antar-service daripada shared-secret header sederhana jika traffic melintasi jaringan publik.
  • Fail-Closed Design: Pastikan middleware internal menolak request jika ada header konteks identitas yang ambigu atau terduplikasi (misal HTTP Parameter Pollution).