Dalam banyak aplikasi Laravel, ada kebutuhan untuk memberikan akses ke suatu route tanpa harus memaksa user login, tetapi tetap ingin memastikan bahwa link tersebut valid dan tidak dimanipulasi. Di sinilah signed URL berguna. Fitur ini memungkinkan Laravel menambahkan tanda tangan kriptografis pada URL sehingga framework bisa memverifikasi apakah parameter pada URL masih utuh atau sudah diubah.

Signed URL cocok untuk skenario seperti link verifikasi email, undangan akses terbatas, tombol unsubscribe newsletter, atau halaman konfirmasi tindakan tertentu. Intinya, signed URL bukan alat autentikasi penuh, tetapi mekanisme untuk menjamin integritas URL.

Apa itu Signed URL di Laravel

Signed URL adalah URL yang memiliki parameter signature hasil perhitungan hash dari isi URL. Laravel membuat signature ini menggunakan kunci aplikasi, lalu saat route diakses, Laravel akan mengecek apakah URL tersebut masih sesuai dengan signature tadi.

Kalau ada parameter yang diubah, misalnya user_id, email, atau expires, maka validasi akan gagal dan request dianggap tidak sah. Ini penting untuk mencegah user memodifikasi link secara manual.

Catatan: Signed URL memastikan URL tidak diubah, tetapi tidak otomatis memastikan siapa yang membuka URL. Jadi, kalau link jatuh ke orang lain, orang itu tetap bisa mengaksesnya selama signature valid dan belum kedaluwarsa.

Kapan Sebaiknya Menggunakan Signed URL

Fitur ini paling berguna saat Anda perlu memberikan akses berdasarkan link yang aman, tetapi tidak ingin membuat flow login penuh. Beberapa contoh kasus yang umum:

  • Verifikasi email setelah registrasi.
  • Link unsubscribe pada email newsletter.
  • Undangan menuju halaman tertentu dengan parameter yang tidak boleh diubah.
  • Konfirmasi aksi, misalnya menyetujui dokumen atau mengaktifkan akun.
  • Akses sementara ke resource publik tertentu.

Jika kebutuhan Anda adalah memastikan identitas pengguna secara penuh, signed URL biasanya perlu digabung dengan autentikasi, otorisasi, atau token yang disimpan di database.

Cara Membuat Signed URL

Laravel menyediakan dua cara utama: URL bertanda tangan permanen dan URL bertanda tangan sementara.

1. Signed URL biasa

Gunakan URL::signedRoute() jika link tidak perlu masa berlaku.

use Illuminate\Support\Facades\URL;

$url = URL::signedRoute('unsubscribe', [
    'user' => 15,
]);

Contoh hasilnya kira-kira seperti ini:

https://example.com/unsubscribe/15?signature=...

Jika ada yang mengganti 15 menjadi angka lain, signature menjadi tidak valid.

2. Temporary signed URL

Gunakan URL::temporarySignedRoute() jika link hanya boleh dipakai dalam periode tertentu.

use Illuminate\Support\Facades\URL;
use Illuminate\Support\Carbon;

$url = URL::temporarySignedRoute(
    'verification.verify',
    Carbon::now()->addMinutes(30),
    ['id' => 15]
);

Laravel akan menambahkan parameter expires. Setelah waktu habis, URL tidak lagi valid meskipun tidak diubah.

Pilihan temporary signed URL umumnya lebih aman untuk tautan sensitif karena mengurangi risiko link lama dipakai ulang.

Melindungi Route dengan Middleware

Setelah URL dibuat, route tujuan harus memverifikasi tanda tangan tersebut. Laravel menyediakan middleware signed untuk kebutuhan ini.

use App\Http\Controllers\UnsubscribeController;
use Illuminate\Support\Facades\Route;

Route::get('/unsubscribe/{user}', [UnsubscribeController::class, 'show'])
    ->name('unsubscribe')
    ->middleware('signed');

Dengan middleware ini, Laravel akan otomatis menolak request dengan signature tidak valid. Jika URL sudah dimodifikasi atau kedaluwarsa, request akan menghasilkan error 403.

Anda juga bisa memeriksa validitas secara manual di controller jika perlu logika khusus:

public function show(Request $request, $user)
{
    if (! $request->hasValidSignature()) {
        abort(403, 'Link tidak valid atau sudah kedaluwarsa.');
    }

    // lanjutkan proses
}

Pendekatan middleware lebih rapi untuk mayoritas kasus, sedangkan pengecekan manual berguna jika ingin respons khusus atau fallback tertentu.

Contoh Kasus Nyata: Link Unsubscribe Email

Salah satu contoh paling sederhana adalah tombol unsubscribe di email newsletter. Anda ingin user bisa berhenti berlangganan cukup dengan satu klik, tanpa login, tetapi Anda juga tidak ingin orang lain menebak URL lalu mengganti ID user seenaknya.

Membuat link di mail atau service

use Illuminate\Support\Facades\URL;

$unsubscribeUrl = URL::temporarySignedRoute(
    'unsubscribe',
    now()->addHours(24),
    ['user' => $user->id]
);

Route tujuan

Route::get('/unsubscribe/{user}', [UnsubscribeController::class, 'unsubscribe'])
    ->name('unsubscribe')
    ->middleware('signed');

Controller

use App\Models\User;

class UnsubscribeController
{
    public function unsubscribe(User $user)
    {
        $user->update([
            'newsletter_subscribed' => false,
        ]);

        return response()->view('unsubscribe-success');
    }
}

Kenapa ini aman? Karena walaupun route menerima parameter {user}, user lain tidak bisa sekadar mengganti ID di URL. Begitu parameter berubah, signature tidak cocok dan middleware akan memblokir request.

Namun ada satu hal penting: jika link asli bocor atau diteruskan ke orang lain sebelum masa berlakunya habis, orang lain tetap bisa memakainya. Karena itu, signed URL sebaiknya dipakai untuk aksi yang risikonya terukur dan tidak terlalu sensitif, atau dikombinasikan dengan validasi tambahan.

Best Practice yang Perlu Diperhatikan

1. Gunakan masa berlaku untuk link sensitif

Kalau memungkinkan, pilih temporarySignedRoute() daripada signed URL permanen. Ini mengurangi jendela serangan jika link terekspos.

2. Jangan menganggap signed URL sama dengan autentikasi

Signed URL hanya menjamin integritas parameter URL. Ia tidak membuktikan identitas pembuka link. Untuk aksi kritis seperti reset data penting, pembayaran, atau akses dokumen privat, pertimbangkan lapisan keamanan tambahan.

3. Hindari menaruh data sensitif langsung di query string

Signature melindungi perubahan URL, tetapi isi URL tetap terlihat oleh browser, log server, analytics, dan history. Jangan menaruh data rahasia seperti token internal mentah, nomor identitas, atau informasi privat lain tanpa pertimbangan.

4. Pastikan APP_URL dan konfigurasi proxy benar

Pada deployment di balik load balancer, reverse proxy, atau HTTPS terminator, validasi signed URL bisa gagal jika Laravel membaca skema atau host yang berbeda dari URL asli. Ini sering terjadi saat aplikasi menghasilkan URL http tetapi request datang dari https.

Kalau menemui masalah seperti ini, cek konfigurasi APP_URL dan trusted proxy. Ketidaksesuaian host, skema, atau port dapat membuat signature dianggap tidak valid.

Kesalahan Umum dan Tips Debugging

Signature selalu invalid

  • Cek apakah route name yang dipakai saat generate URL sama dengan route yang menerima request.
  • Cek apakah ada parameter route yang berubah urutan atau nilainya.
  • Pastikan domain, skema HTTP/HTTPS, dan port konsisten.
  • Jika menggunakan temporary signed URL, cek apakah waktu server sinkron.

URL valid di lokal, gagal di production

Biasanya terkait proxy atau HTTPS. Framework mungkin menandatangani URL dengan host atau skema yang berbeda dari URL yang benar-benar diakses user.

Link masih bisa dipakai orang lain

Ini bukan bug. Signed URL memang bukan mekanisme identitas. Kalau ingin membatasi hanya pemilik akun tertentu yang bisa melakukan aksi, tambahkan autentikasi atau token unik yang disimpan dan diverifikasi di database.

Kapan Signed URL Cukup, dan Kapan Perlu Solusi Lain

Signed URL cukup ketika Anda hanya ingin memastikan bahwa link tidak dimodifikasi dan, bila perlu, hanya berlaku dalam jangka waktu tertentu. Ini cocok untuk alur sederhana seperti unsubscribe, verifikasi email, atau undangan ringan.

Namun jika Anda butuh kontrol lebih seperti single-use link, pencabutan manual, pelacakan siapa yang sudah memakai link, atau pembatasan ketat per perangkat/pengguna, maka signed URL saja belum cukup. Dalam kasus seperti itu, Anda biasanya perlu menyimpan token di database dan memverifikasi statusnya saat request datang.

Penutup

Signed URL di Laravel adalah fitur kecil yang sangat praktis untuk mengamankan route berbasis tautan. Implementasinya singkat: generate URL dengan signedRoute() atau temporarySignedRoute(), lalu lindungi route dengan middleware signed. Dengan begitu, parameter URL tidak bisa diubah sembarangan tanpa terdeteksi.

Untuk kebutuhan sehari-hari seperti verifikasi email atau unsubscribe newsletter, ini sering kali sudah cukup. Yang penting, pahami batasannya: signed URL menjaga integritas link, bukan identitas pengguna. Jika konteksnya lebih sensitif, tambahkan lapisan keamanan lain sesuai kebutuhan aplikasi Anda.