Pada aplikasi Go Fiber yang memakai session cookie, masalah utamanya bukan sekadar membuat pengguna tetap login, tetapi memastikan cookie tidak mudah dicuri, dipakai lintas situs, atau dipertahankan lewat session fixation. Praktiknya biasanya mencakup empat hal: set atribut cookie dengan benar, rotasi session ID setelah autentikasi berhasil, aktifkan proteksi CSRF saat browser otomatis mengirim cookie, dan pastikan sesi bisa dihapus dengan tegas saat logout.

Artikel ini fokus pada implementasi praktis Go Fiber: session cookie aman, CSRF, dan mitigasi session fixation tanpa melebar ke JWT. Contohnya ditujukan untuk aplikasi server-rendered maupun SPA yang tetap mengandalkan cookie untuk autentikasi.

Model ancaman yang perlu dipahami

Sebelum mengatur middleware, pahami risiko yang ingin dikurangi:

  • Pencurian cookie: terjadi lewat koneksi tanpa TLS, XSS, atau kebocoran log/header.
  • CSRF: browser korban otomatis mengirim cookie ke aplikasi Anda saat ada request dari situs lain.
  • Session fixation: penyerang memaksa korban memakai session ID yang sudah diketahui sebelumnya, lalu mengambil alih sesi setelah korban login.
  • Sesi tidak benar-benar berakhir: cookie di browser terhapus, tetapi data session di server masih valid.

Dengan kata lain, cookie aman bukan hanya soal HttpOnly, tetapi juga soal lifecycle session dari sebelum login, saat login, sampai logout.

Konfigurasi session cookie yang aman di Go Fiber

Atribut cookie yang wajib diperhatikan

Untuk autentikasi berbasis cookie, atribut berikut biasanya perlu diatur:

  • Secure: cookie hanya dikirim lewat HTTPS.
  • HttpOnly: JavaScript di browser tidak bisa membaca cookie langsung.
  • SameSite: membatasi pengiriman cookie pada request lintas situs.
  • TTL / Expiration: membatasi umur sesi.
  • Path dan, bila perlu, Domain: membatasi cakupan cookie.

Prinsip amannya:

  • Gunakan Secure=true di produksi.
  • Gunakan HttpOnly=true untuk cookie session.
  • Mulai dari SameSite=Lax untuk banyak aplikasi web umum.
  • Pakai SameSite=None hanya jika memang perlu cookie dikirim lintas situs, dan itu mengharuskan Secure=true.
  • Batasi TTL sesuai kebutuhan bisnis; jangan buat sesi terlalu panjang tanpa alasan.

Catatan: SameSite membantu mengurangi CSRF, tetapi bukan pengganti total proteksi CSRF untuk semua pola aplikasi.

Contoh konfigurasi session store

Nama paket dan detail API bisa berbeda tergantung susunan middleware yang Anda pakai, tetapi pola umumnya tetap sama: buat store session, atur cookie, lalu gunakan di handler login/logout.

package main

import (
    "log"
    "time"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/encryptcookie"
    "github.com/gofiber/storage/redis"
    "github.com/gofiber/fiber/v2/middleware/session"
)

func main() {
    app := fiber.New()

    // Opsional: mengenkripsi nilai cookie di level aplikasi.
    // Ini bukan pengganti TLS, tetapi membantu mengurangi paparan isi cookie.
    app.Use(encryptcookie.New(encryptcookie.Config{
        Key: "ganti-dengan-kunci-32-byte-yang-aman!!",
    }))

    // Contoh Redis storage. Untuk development kecil, memory bisa dipakai,
    // tetapi produksi multi-instance biasanya butuh shared storage.
    redisStore := redis.New(redis.Config{
        Host:     "127.0.0.1",
        Port:     6379,
        Database: 0,
    })

    store := session.New(session.Config{
        Storage:        redisStore,
        CookieHTTPOnly: true,
        CookieSecure:   true,
        CookieSameSite: "Lax",
        Expiration:     30 * time.Minute,
        KeyLookup:      "cookie:session_id",
    })

    app.Post("/login", func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            return fiber.ErrInternalServerError
        }

        // Validasi kredensial pengguna di sini.
        userID := "123"

        // Rotasi session ID akan dibahas di bagian berikut.
        sess.Set("user_id", userID)
        if err := sess.Save(); err != nil {
            return fiber.ErrInternalServerError
        }

        return c.JSON(fiber.Map{"ok": true})
    })

    app.Post("/logout", func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            return fiber.ErrInternalServerError
        }
        if err := sess.Destroy(); err != nil {
            return fiber.ErrInternalServerError
        }
        return c.SendStatus(fiber.StatusNoContent)
    })

    log.Fatal(app.Listen(":3000"))
}

Hal yang perlu diperhatikan dari contoh di atas:

  • TTL session harus masuk akal. Untuk area sensitif seperti dashboard admin, durasi lebih pendek sering lebih aman.
  • Nama cookie jangan terlalu generik bila Anda mengelola beberapa aplikasi pada domain yang sama.
  • Domain cookie jangan diperluas ke seluruh subdomain bila tidak diperlukan.

Mitigasi session fixation: rotasi session ID setelah login

Mengapa rotasi session ID penting

Session fixation terjadi ketika attacker sudah mengetahui atau menanamkan session ID tertentu sebelum korban login. Jika aplikasi mempertahankan ID yang sama setelah login berhasil, attacker bisa memakai ID itu untuk mengakses sesi korban.

Mitigasinya sederhana secara konsep: jangan pertahankan session ID lama setelah autentikasi berhasil. Saat login sukses, buat sesi baru atau regenerasi ID, lalu pindahkan data yang memang perlu dipertahankan.

Pola implementasi yang aman

Pola yang paling aman dan mudah diaudit:

  1. Ambil session anonim yang mungkin sudah ada.
  2. Setelah kredensial valid, destroy/regenerate session lama.
  3. Buat session baru dengan ID baru.
  4. Simpan identitas login dan metadata penting.
  5. Simpan session baru ke store.

Secara praktis, jika middleware session yang Anda pakai tidak menyediakan API regenerasi ID yang jelas, gunakan pola hapus sesi lama lalu buat sesi baru. Intinya adalah memastikan identifier setelah login berbeda dari sebelum login.

app.Post("/login", func(c *fiber.Ctx) error {
    oldSess, err := store.Get(c)
    if err != nil {
        return fiber.ErrInternalServerError
    }

    // 1) Validasi kredensial
    email := c.FormValue("email")
    password := c.FormValue("password")
    userID, ok := authenticate(email, password)
    if !ok {
        return fiber.ErrUnauthorized
    }

    // 2) Hancurkan session lama untuk mencegah fixation
    _ = oldSess.Destroy()

    // 3) Ambil/buat session baru
    newSess, err := store.Get(c)
    if err != nil {
        return fiber.ErrInternalServerError
    }

    // 4) Simpan data minimum yang dibutuhkan
    newSess.Set("user_id", userID)
    newSess.Set("issued_at", time.Now().Unix())

    if err := newSess.Save(); err != nil {
        return fiber.ErrInternalServerError
    }

    return c.JSON(fiber.Map{"ok": true})
})

func authenticate(email, password string) (string, bool) {
    if email == "[email protected]" && password == "secret" {
        return "123", true
    }
    return "", false
}

Jika Anda menyimpan data pra-login seperti bahasa atau preferensi UI, jangan pindahkan semuanya secara membabi buta ke sesi baru. Pindahkan hanya data yang aman dan memang perlu dipertahankan.

Kapan lagi perlu rotasi session ID

Selain setelah login, pertimbangkan rotasi session ID saat:

  • privilege pengguna berubah, misalnya dari user biasa ke mode admin;
  • pengguna menyelesaikan verifikasi tambahan;
  • terjadi pemulihan akun atau perubahan password sensitif.

Kapan CSRF protection wajib pada aplikasi Fiber

Aturan praktis yang paling berguna

Anda perlu CSRF protection bila browser akan otomatis mengirim kredensial ke server, terutama cookie session. Ini berlaku untuk:

  • aplikasi server-rendered dengan form POST/PUT/DELETE;
  • SPA yang memanggil API dan tetap memakai cookie untuk autentikasi;
  • endpoint yang mengubah state: login, logout, update profil, ganti password, checkout, dan sejenisnya.

Anda biasanya tidak bergantung pada CSRF jika kredensial dikirim manual lewat header yang tidak otomatis dikirim browser, tetapi itu di luar fokus artikel ini karena kita membahas session cookie.

Mengapa SameSite saja tidak cukup

SameSite=Lax memang membantu mengurangi request lintas situs tertentu, tetapi tidak sebaik token CSRF untuk kontrol yang eksplisit. Selain itu, perilaku browser, alur redirect, integrasi lintas domain, dan kebutuhan kompatibilitas kadang membuat Anda tidak bisa hanya mengandalkan atribut cookie.

Untuk operasi yang mengubah data, gunakan token CSRF sebagai lapisan tambahan.

Pola token CSRF yang masuk akal di Fiber

Pola synchronizer token untuk server-rendered

Pada aplikasi server-rendered, pola yang umum adalah:

  1. Server membuat token CSRF dan menyimpannya di session.
  2. Token dimasukkan ke form sebagai hidden input.
  3. Saat form dikirim, server membandingkan token dari request dengan token di session.

Ini cocok karena server sudah mengelola state session.

func CSRFMiddleware(store *session.Store) fiber.Handler {
    return func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            return fiber.ErrInternalServerError
        }

        // Untuk method aman, pastikan token tersedia.
        switch c.Method() {
        case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions:
            if sess.Get("csrf_token") == nil {
                token := generateToken()
                sess.Set("csrf_token", token)
                if err := sess.Save(); err != nil {
                    return fiber.ErrInternalServerError
                }
            }
            c.Locals("csrf_token", sess.Get("csrf_token"))
            return c.Next()
        }

        // Untuk method yang mengubah state, verifikasi token.
        requestToken := c.FormValue("csrf_token")
        sessionToken, _ := sess.Get("csrf_token").(string)
        if requestToken == "" || sessionToken == "" || requestToken != sessionToken {
            return fiber.NewError(fiber.StatusForbidden, "invalid csrf token")
        }

        return c.Next()
    }
}

Di template HTML, token tersebut dimasukkan ke form:

<form method="post" action="/profile">
  <input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
  <input type="text" name="display_name">
  <button type="submit">Simpan</button>
</form>

Pola double-submit cookie untuk SPA yang memakai cookie

Untuk SPA, pola yang sering masuk akal adalah double-submit cookie:

  1. Server membuat token CSRF acak.
  2. Server mengirim token itu ke browser dalam cookie terpisah yang tidak HttpOnly, agar JavaScript bisa membacanya.
  3. Frontend membaca token tersebut lalu mengirimkannya kembali lewat header, misalnya X-CSRF-Token.
  4. Server membandingkan nilai cookie CSRF dengan header.

Cookie session tetap HttpOnly, sedangkan cookie CSRF memang perlu bisa dibaca frontend. Ini bukan kontradiksi, karena token CSRF fungsinya berbeda dari session ID.

func IssueCSRFCookie() fiber.Handler {
    return func(c *fiber.Ctx) error {
        token := generateToken()

        c.Cookie(&fiber.Cookie{
            Name:     "csrf_token",
            Value:    token,
            HTTPOnly: false,
            Secure:   true,
            SameSite: "Lax",
            Path:     "/",
        })

        return c.Next()
    }
}

func VerifyDoubleSubmitCSRF() fiber.Handler {
    return func(c *fiber.Ctx) error {
        switch c.Method() {
        case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions:
            return c.Next()
        }

        cookieToken := c.Cookies("csrf_token")
        headerToken := c.Get("X-CSRF-Token")
        if cookieToken == "" || headerToken == "" || cookieToken != headerToken {
            return fiber.NewError(fiber.StatusForbidden, "invalid csrf token")
        }

        return c.Next()
    }
}

Contoh pengiriman dari frontend:

const csrfToken = document.cookie
  .split('; ')
  .find(row => row.startsWith('csrf_token='))
  ?.split('=')[1];

await fetch('/api/profile', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken || ''
  },
  body: JSON.stringify({ display_name: 'Budi' })
});

Trade-off pola ini:

  • Kelebihan: mudah diterapkan pada SPA yang sudah memakai cookie.
  • Keterbatasan: token CSRF bisa dibaca JavaScript, jadi tidak melindungi dari XSS. CSRF dan XSS adalah ancaman berbeda; Anda tetap perlu sanitasi output, CSP bila relevan, dan pengendalian input.

Middleware autentikasi, login, logout, dan invalidasi session

Middleware pengecekan session

Middleware autentikasi sebaiknya memverifikasi bahwa session benar-benar memiliki identitas pengguna yang valid. Jangan anggap keberadaan cookie saja sudah cukup.

func RequireAuth(store *session.Store) fiber.Handler {
    return func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            return fiber.ErrInternalServerError
        }

        userID, ok := sess.Get("user_id").(string)
        if !ok || userID == "" {
            return fiber.ErrUnauthorized
        }

        c.Locals("user_id", userID)
        return c.Next()
    }
}

Alur login yang disarankan

  1. Terima kredensial.
  2. Validasi input dan batasi brute force sesuai kebutuhan.
  3. Autentikasi ke database/identity store.
  4. Rotasi session ID.
  5. Simpan hanya data session yang dibutuhkan, misalnya user_id, role ringkas, dan waktu terbit sesi.
  6. Kirim respons sukses.

Hindari menyimpan terlalu banyak data sensitif di session bila tidak perlu. Semakin banyak state, semakin sulit invalidasi dan audit-nya.

Alur logout yang benar

Logout yang aman bukan hanya menghapus cookie di browser. Anda juga perlu menghapus data session di server-side store.

app.Post("/logout", RequireAuth(store), func(c *fiber.Ctx) error {
    sess, err := store.Get(c)
    if err != nil {
        return fiber.ErrInternalServerError
    }

    if err := sess.Destroy(); err != nil {
        return fiber.ErrInternalServerError
    }

    // Jika Anda punya cookie CSRF terpisah, hapus juga.
    c.Cookie(&fiber.Cookie{
        Name:     "csrf_token",
        Value:    "",
        Expires:  time.Unix(0, 0),
        MaxAge:   -1,
        Path:     "/",
        HTTPOnly: false,
        Secure:   true,
        SameSite: "Lax",
    })

    return c.SendStatus(fiber.StatusNoContent)
})

Jika aplikasi mendukung logout from all devices, Anda perlu model data yang memungkinkan invalidasi semua sesi milik satu user, bukan hanya sesi aktif saat ini.

Memory vs Redis untuk penyimpanan session

Kapan memory cukup

Penyimpanan memory cocok untuk:

  • development lokal;
  • aplikasi kecil satu instance;
  • testing sederhana.

Keterbatasannya jelas:

  • sesi hilang saat proses restart;
  • tidak cocok untuk banyak instance/container tanpa sticky session;
  • sulit untuk invalidasi terpusat.

Kapan Redis lebih tepat

Redis biasanya lebih cocok untuk produksi ketika:

  • aplikasi berjalan di beberapa instance;
  • Anda ingin session tetap hidup saat satu instance restart;
  • Anda butuh TTL terkelola dan invalidasi yang lebih konsisten;
  • Anda ingin observabilitas sesi yang lebih baik.

Trade-off Redis:

  • ada dependensi jaringan tambahan;
  • harus mengelola availability, timeout, dan keamanan koneksi Redis;
  • latensi sedikit lebih tinggi dibanding memory lokal, walau sering layak untuk manfaat operasionalnya.

Untuk kebanyakan deployment produksi yang diskalakan horizontal, Redis lebih masuk akal daripada memory store.

Checklist hardening produksi di balik reverse proxy dan TLS

Banyak bug keamanan session bukan berasal dari kode login, melainkan dari deployment yang salah membaca skema HTTP/HTTPS atau salah meneruskan header proxy.

Checklist yang sebaiknya diperiksa

  • TLS aktif end-to-end atau setidaknya sampai reverse proxy tepercaya.
  • Cookie Secure aktif di produksi.
  • Fiber mengetahui request asli datang lewat HTTPS bila berada di balik reverse proxy, agar logika keamanan tidak salah menilai koneksi sebagai HTTP biasa.
  • Proxy hanya dipercaya jika memang milik Anda; jangan menerima header forwarding secara membabi buta dari internet publik.
  • Set header keamanan yang relevan, misalnya HSTS bila seluruh domain sudah siap dipaksa HTTPS.
  • Batasi domain dan path cookie seminimal mungkin.
  • TTL session masuk akal dan ada kebijakan idle/absolute timeout bila dibutuhkan.
  • Rotasi session ID setelah login dan privilege change.
  • Logout menghapus sesi di server, bukan hanya di client.
  • Proteksi CSRF aktif untuk endpoint state-changing yang memakai cookie.
  • Log sensitif dibersihkan; jangan log cookie, token CSRF, atau header autentikasi mentah.
  • Rate limiting dan audit login gagal tersedia untuk mengurangi brute force.

Catatan reverse proxy: jika terminasi TLS dilakukan di Nginx, Traefik, atau load balancer, pastikan aplikasi hanya memercayai header seperti X-Forwarded-Proto dari proxy yang tepercaya. Kalau salah konfigurasi, aplikasi bisa gagal menerapkan asumsi HTTPS dan cookie aman.

Kesalahan umum dan tips debugging

Cookie tidak tersimpan di browser

  • Secure=true tetapi Anda masih menguji lewat HTTP biasa.
  • SameSite tidak cocok dengan pola request lintas origin yang Anda gunakan.
  • Domain atau Path cookie terlalu sempit atau salah.

User terasa logout sendiri

  • TTL terlalu pendek.
  • Store memory dipakai di environment yang sering restart.
  • Aplikasi berjalan di banyak instance tanpa shared session store.

CSRF gagal walau login berhasil

  • Frontend lupa mengirim credentials: 'include'.
  • Header token CSRF tidak dikirim pada request mutasi.
  • Token di session/cookie sudah diganti tetapi halaman lama masih mengirim nilai lama.

Rotasi session tidak efektif

  • Session lama tidak benar-benar dihancurkan di store.
  • Data autentikasi masih dipertahankan pada ID lama.
  • Ada cache atau alur login yang membuat cookie lama tetap aktif.

Saat debugging, periksa tiga hal sekaligus: Set-Cookie pada response, cookie yang benar-benar tersimpan di browser, dan data session yang ada di store. Sering kali masalah ada pada salah satu lapisan itu, bukan semuanya.

Ringkasan implementasi yang disarankan

Jika Anda ingin baseline yang aman untuk autentikasi berbasis session cookie di Fiber, gunakan pendekatan berikut:

  • Cookie session dengan Secure, HttpOnly, SameSite=Lax sebagai titik awal.
  • TTL session yang wajar, jangan terlalu panjang.
  • Rotasi session ID setelah login untuk mencegah session fixation.
  • Aktifkan CSRF protection untuk request yang mengubah state bila browser otomatis mengirim cookie.
  • Logout harus menghapus session di server-side store.
  • Untuk produksi multi-instance, pilih Redis dibanding memory store.
  • Pastikan deployment di balik reverse proxy tetap mempertahankan asumsi HTTPS dengan benar.

Dengan kombinasi ini, aplikasi Go Fiber Anda tidak hanya berfungsi, tetapi juga jauh lebih tahan terhadap serangan umum pada autentikasi berbasis cookie.