Laravel memudahkan melindungi aplikasi kita dari serangan cross-site request forgery (CSRF). Cross-site request forgeries adalah jenis eksploit berbahaya di mana perintah yang tidak sah dilakukan atas nama pengguna yang diautentikasi. Pada artikel ini kita akan membahas bagaimana menangani 419 error / Page Expired pada Laravel (TokenMismatchException).
Laravel secara otomatis membuat “token” CSRF untuk setiap sesi pengguna aktif yang dikelola oleh aplikasi. Token ini digunakan untuk memverifikasi bahwa pengguna yang diautentikasi adalah yang benar-benar membuat permintaan ke aplikasi. Token inilah yang menyebabkan suatu aplikasi bisa menghasilkan 419 Error / Page Expired pada Laravel.
419 Error / Page Expired pada Laravel biasa terjadi pada salah satu halaman yang memiliki form. Misalnya, kita memiliki form seperti dibawah ini pada salah satu halaman pada aplikasi kita.
<form method="POST" action="/profile"> Name: <input type="text" name="name"/> <button type="submit" value="Submit">Submit</button> </form>
Ketika men-submit form diatas, Laravel akan memeriksa token CSRF, jika token tidak ditemukan / expired maka Laravel akan menganggap proses ini tidak dilakukan oleh pengguna yang diautentikasi dan akan mengembalikan error 419 Error / Page Expired.
Laravel 419 Error / Page Expired
Lalu, bagaimana agar form diatas bisa di proses dengan benar? Setiap kali kita membuat Form HTML pada aplikasi kita, kita harus menyertakan sebuah hidden input CSRF token dengan nama field _token
. Untuk menambahkan hidden input ini kita bisa menggunakan blade directive @csrf
.
<form method="POST" action="/profile"> @csrf Name: <input type="text" name="name"/> <button type="submit" value="Submit">Submit</button> </form>
Bagaimana Laravel Memeriksa token CSRF
Laravel memeriksa token ini menggunakan VerifyCsrfToken Middleware (App\Http\Middleware\VerifyCsrfToken) yang meng-extend class middleware bawaan dari Laravel (Illuminate\Foundation\Http\Middleware\VerifyCsrfToken). Mari kita coba melihat bagaimana Laravel memeriksa token ini di backend.
Pada class Illuminate\Foundation\Http\Middleware\VerifyCsrfToken Laravel memiliki sebuah method bernama handle
yang mana setiap middleware pada Laravel akan mengeksekusi method handle
untuk memeriksa request.
public function handle($request, Closure $next) { if ( $this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request) ) { return tap($next($request), function ($response) use ($request) { if ($this->shouldAddXsrfTokenCookie()) { $this->addCookieToResponse($request, $response); } }); } throw new TokenMismatchException('CSRF token mismatch.'); }
Bisa kita lihat pada method diatas, Laravel memiliki beberapa kondisi yang harus terpenuhi untuk memastikan bahwa request dilakukan oleh pengguna yang diautentikasi. Jika salah satu kondisi diatas terpenuhi, Laravel akan meneruskan request kita, akan tetapi jika kondisi diatas tidak terpenuhi, Laravel akan menampilkan error TokenMismatchException. Kita akan membahas satu per satu kondisi diatas.
isReading
Laravel akan mengabaikan pemeriksaan token CSRF jika request method yang dilakukan merupakan salah satu dari method HEAD, GET atau OPTIONS.
protected function isReading($request) { return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']); }
runningUnitTests
Method ini akan memeriksa jika request dilakukan di console dan mengeksekusi test. Laravel akan mengabaikan pemeriksaan CSRF token jika request dilakukan saat mengeksekusi test.
protected function runningUnitTests() { return $this->app->runningInConsole() && $this->app->runningUnitTests(); }
inExceptArray
Laravel VerifyCsrfToken middleware memberikan opsi pada kita untuk mengabaikan beberapa url dari pemeriksaan CSRF. Ketika sebuah URL / request diabaikan pada opsi ini, Laravel akan mengabaikan pemeriksaan CSRF. Kita akan membahas kapan kita harus menggunakan opsi ini pada akhir artikel ini.
protected function inExceptArray($request) { foreach ($this->except as $except) { if ($except !== '/') { $except = trim($except, '/'); } if ($request->fullUrlIs($except) || $request->is($except)) { return true; } } return false; }
tokensMatch
Method ini lah yang merupakan kunci dari pemeriksaan token CSRF pada aplikasi kita. Method ini akan memeriksa apakah token yang tersimpan pada session dan pada input CSRF sama atau tidak.
protected function tokensMatch($request) { $token = $this->getTokenFromRequest($request); return is_string($request->session()->token()) && is_string($token) && hash_equals($request->session()->token(), $token); }
Mengabaikan Pemeriksaan CSRF
Seperti telah kita bahas sebelumnya, Laravel memberikan kita opsi untuk mengabaikan beberapa URL untuk di periksa. Untuk beberapa kasus kita mungkin perlu mengabaikan beberapa URL dari pemeriksaan CSRF. Untuk mengabaikan pemeriksaan CSRF token, VerifyCsrfToken middleware memiliki property $except
untuk mendaftarkan list url yang harus di abaikan dari pemeriksaan.
/** * The URIs that should be excluded from CSRF verification. * * @var array */ protected $except = [];
Pertanyaannya adalah kapan kita harus mengabaikan sebuah URL dari pemeriksaan CSRF token? Jawabannya adalah ketika kita tidak perlu pemeriksaan CSRF pada URL tersebut :D. Biasanya URL aplikasi kita yang di akses oleh external service harus kita abaikan dari pemeriksaan CSRF. Misalnya ketika kita membuat sebuah URL untuk di konsumsi oleh aplikasi lain. Sebagai contoh, URL untuk menangani Stripe Webhook yang mana Stripe akan mengakses URL webhook kita untuk mengirimkan beberapa informasi terkait dengan event yang terjadi pada Stripe.
Diasumsikan bahwa kita memiliki URL stripe/webhook_url untuk menangani informasi yang dikirimkan oleh stripe. Maka pada VerifyCsrfMiddleware kita harus mendaftarkan url tersebut pada property $except
.
protected $except = [ 'stripe/webhok_url', ];
People reacted to this story.
Show comments Hide commentsKalau misalnya kita buat satu form, misalnya utk buat artikel semacam blog.
User kan bakal lama tuh di form tsb karena harus mengarang artikelnya, terus pas di-submit eee kena error \”419 | Page Expired\” dan pas coba reload page malah hilang semua konten yg sudah diisi di form tsb.
Kira2 solusinya apa ya klo begitu?
Bisa di naikin session lifetimenya.
config/session.php
atau bisa diabaikan route tersebut dari VerifyCsrfToken middleware.
app/Http/Middleware/VerifyCsrfToken.php