Cookie adalah kurir kredensial: session ID dari chapter sebelumnya berpindah di dalamnya pada setiap permintaan. Kurir tanpa pengawalan justru membocorkan muatan — cookie tanpa HttpOnly dapat dibaca JavaScript jahat, tanpa Secure ikut terkirim lewat HTTP polos, tanpa SameSite dikirim oleh situs lain tanpa sepengetahuan pengguna. Struct http.Cookie menyediakan seluruh atribut pengaman itu; chapter ini membedah atributnya lalu membuktikan bentuknya lewat respons httptest.
Mengatur Cookie dengan http.SetCookie #
Fungsi http.SetCookie(w, cookie) menerima http.ResponseWriter dan pointer http.Cookie, lalu menulis satu header Set-Cookie yang terformat benar; pemanggilan berikutnya menambah baris baru, jadi satu respons boleh membawa banyak cookie. Pembacaan kembali memakai r.Cookie("nama") untuk satu cookie atau resp.Cookies() untuk semua cookie dalam respons.
Atribut Keamanan pada http.Cookie #
Setiap field pada http.Cookie mengendalikan satu perilaku browser. Tabel berikut merangkum atribut yang menentukan keamanan:
| Atribut | Efek pada browser | Nilai aman untuk cookie sesi |
|---|---|---|
HttpOnly | JavaScript tidak dapat membaca cookie lewat document.cookie | true |
Secure | Cookie hanya dikirim melalui HTTPS | true |
SameSite | Membatasi pengiriman cookie lintas situs | Lax atau Strict |
Domain | Menentukan host mana yang menerima cookie | kosong (host saat ini) |
Path | Membatasi path URL yang menerima cookie | / atau sempit |
MaxAge | Umur cookie dalam detik | sesuai kebutuhan |
Expires | Batas waktu absolut cookie | sesuai kebutuhan |
HttpOnly menutup jalur pencurian lewat XSS: skrip yang tersisipkan ke halaman tetap tidak bisa membaca session ID, sehingga serangan tinggal memakai cookie dari dalam browser — penanganannya di chapter CSRF. Secure menjamin cookie tidak pernah melintas di jaringan terbuka yang bisa dibaca siapa pun. Domain dan Path mempersempit jangkauan: cookie dengan Path: "/admin" tidak dikirim ke endpoint lain, dan Domain yang terlalu luas membuat cookie terkirim ke semua subdomain.
SameSite Lax, Strict, dan None #
SameSite mengatur apakah browser tetap melampirkan cookie ketika permintaan berasal dari situs lain. Strict tidak pernah mengirimnya — tautan dari situs lain pun tiba tanpa login. Lax berkompromi: navigasi GET tingkat atas tetap membawa cookie, sedangkan POST lintas situs tidak. None mengirim cookie di semua konteks dan hanya sah untuk kasus seperti pembayaran terarah ke domain lain.
Cookie Sesi dan Cookie Persisten #
Cookie tanpa MaxAge dan tanpa Expires adalah cookie sesi: tersimpan hanya di memori dan musnah saat jendela ditutup. Cookie dengan salah satu batas waktu menjadi cookie persisten di disk. MaxAge menyatakan umur relatif dalam detik, Expires menyatakan waktu absolut, dan bila keduanya diisi MaxAge yang menang. MaxAge negatif atau Expires di masa lalu memerintahkan browser menghapus cookie — pola yang dipakai handler logout pada chapter sebelumnya.
Prefix __Host- #
Nama cookie dapat menjadi kontrak keamanan. Prefix __Host- — misalnya __Host-tema — memaksa browser menolak cookie kecuali disertai Secure, Path=/, dan tanpa Domain; cookie itu dijamin berasal dari host yang tepat dan tidak dapat ditimpa subdomain jahat. Aturannya bekerja murni lewat penamaan.
Program: Membaca Cookie Balasan #
Program cookie-demo.go mengatur empat cookie berbeda, mencetak header Set-Cookie mentah beserta parsing atributnya, lalu menyimulasikan kunjungan kedua tempat seluruh cookie dikirim balik ke server.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
)
// aturCookie menulis empat cookie dengan konfigurasi berbeda ke respons.
func aturCookie(w http.ResponseWriter, r *http.Request) {
// Cookie sesi: tanpa MaxAge/Expires, hilang saat browser ditutup.
http.SetCookie(w, &http.Cookie{
Name: "sesi",
Value: "sesi-budi-aktif",
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// Cookie persisten: bertahan 3600 detik di penyimpanan browser.
http.SetCookie(w, &http.Cookie{
Name: "preferensi",
Value: "tema-gelap",
Path: "/",
MaxAge: 3600,
})
// Prefix __Host-: wajib Secure, Path=/, tanpa Domain.
http.SetCookie(w, &http.Cookie{
Name: "__Host-tema",
Value: "biru",
Path: "/",
Secure: true,
})
// Cookie dengan batas waktu absolut lewat Expires.
http.SetCookie(w, &http.Cookie{
Name: "kupon",
Value: "DISKON20",
Path: "/",
Expires: time.Date(2026, 1, 16, 8, 0, 0, 0, time.UTC),
})
}
func bacaCookie(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("sesi")
if err != nil {
http.Error(w, "cookie sesi tidak ditemukan", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "nilai cookie sesi: %s", c.Value)
}
func namaSameSite(s http.SameSite) string {
switch s {
case http.SameSiteLaxMode:
return "Lax"
case http.SameSiteStrictMode:
return "Strict"
case http.SameSiteNoneMode:
return "None"
default:
return "default"
}
}
func deskripsi(c *http.Cookie) string {
maxAge := fmt.Sprintf("%d", c.MaxAge)
if c.MaxAge == 0 {
maxAge = "0 (cookie sesi)"
}
expires := c.Expires.Format("2006-01-02 15:04 MST")
if c.Expires.IsZero() {
expires = "tidak diset"
}
return fmt.Sprintf("HttpOnly=%-5v Secure=%-5v SameSite=%-7s Path=%s MaxAge=%s Expires=%s",
c.HttpOnly, c.Secure, namaSameSite(c.SameSite), c.Path, maxAge, expires)
}
func main() {
// Permintaan pertama: server mengatur empat cookie sekaligus.
req := httptest.NewRequest("GET", "/masuk", nil)
rec := httptest.NewRecorder()
aturCookie(rec, req)
resp := rec.Result()
fmt.Println("header Set-Cookie mentah dari respons:")
for _, v := range resp.Header["Set-Cookie"] {
fmt.Println(" " + v)
}
// Parsing kembali atribut setiap cookie dari respons yang sama.
fmt.Println()
fmt.Println("atribut hasil parsing setiap cookie:")
cookies := resp.Cookies()
for _, c := range cookies {
fmt.Printf(" %-12s %s\n", c.Name, deskripsi(c))
}
// Kunjungan kedua: browser mengirim semua cookie kembali ke server.
fmt.Println()
fmt.Println("kunjungan kedua, cookie yang terkirim balik:")
reqKedua := httptest.NewRequest("GET", "/baca", nil)
for _, c := range cookies {
fmt.Printf(" %-12s = %s\n", c.Name, c.Value)
reqKedua.AddCookie(c)
}
recKedua := httptest.NewRecorder()
bacaCookie(recKedua, reqKedua)
fmt.Println()
fmt.Println("handler /baca membalas :", recKedua.Body.String())
}Output yang Dihasilkan:
header Set-Cookie mentah dari respons:
sesi=sesi-budi-aktif; Path=/; HttpOnly; SameSite=Lax
preferensi=tema-gelap; Path=/; Max-Age=3600
__Host-tema=biru; Path=/; Secure
kupon=DISKON20; Path=/; Expires=Fri, 16 Jan 2026 08:00:00 GMT
atribut hasil parsing setiap cookie:
sesi HttpOnly=true Secure=false SameSite=Lax Path=/ MaxAge=0 (cookie sesi) Expires=tidak diset
preferensi HttpOnly=false Secure=false SameSite=default Path=/ MaxAge=3600 Expires=tidak diset
__Host-tema HttpOnly=false Secure=true SameSite=default Path=/ MaxAge=0 (cookie sesi) Expires=tidak diset
kupon HttpOnly=false Secure=false SameSite=default Path=/ MaxAge=0 (cookie sesi) Expires=2026-01-16 08:00 UTC
kunjungan kedua, cookie yang terkirim balik:
sesi = sesi-budi-aktif
preferensi = tema-gelap
__Host-tema = biru
kupon = DISKON20
handler /baca membalas : nilai cookie sesi: sesi-budi-aktifMari kita telaah baris demi baris program di atas:
- Baris 12-19: cookie
sesitanpaMaxAge/Expiresmenjadi cookie sesi;HttpOnlydanSameSite=Laxmelindunginya dari JavaScript dan POST lintas situs. - Baris 20-26: cookie
preferensimemakaiMaxAge: 3600sehingga bertahan satu jam di disk. - Baris 27-33: cookie
__Host-temamemenuhi kontrak prefix —SecuredanPath: "/"tanpaDomain. - Baris 34-40: cookie
kuponmemakaiExpiresabsolut dengantime.Datebeku agar output contoh deterministik. - Baris 80-88:
httptest.NewRecordermenampung respons tanpa jaringan;resp.Header["Set-Cookie"]memperlihatkan format mentah tulisanhttp.SetCookie. - Baris 93-96:
resp.Cookies()mem-parsing header kembali menjadi struct, laludeskripsimencetak atributnya — perhatikan cookie tanpaSameSiteterbaca sebagaidefault. - Baris 101-105: permintaan kedua memanggil
AddCookieuntuk setiap cookie, meniru browser yang melampirkan seluruh cookie domain. - Baris 107-110: handler
bacaCookiemembaca cookiesesidari permintaan kedua dan membuktikan nilainya utuh.
Referensi #
- net/http: SetCookie — dokumentasi resmi penulisan cookie.
- Package net/http — struct Cookie, CookieError, dan parsing header.
- OWASP Session Management Cheat Sheet — aturan atribut cookie untuk sesi.
Cookie yang terkonfigurasi baik menjaga kredensial selama perjalanan bolak-balik, tetapi pendekatan ini tetap menuntut server menyimpan status setiap pengguna. Bila Anda ingin server memverifikasi identitas tanpa menyimpan apa pun, kita masuk ke token bertanda tangan pada chapter Token-Based Authentication berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
cookie-demo.go
Unduh berkaspackage main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
)
// aturCookie menulis empat cookie dengan konfigurasi berbeda ke respons.
func aturCookie(w http.ResponseWriter, r *http.Request) {
// Cookie sesi: tanpa MaxAge/Expires, hilang saat browser ditutup.
http.SetCookie(w, &http.Cookie{
Name: "sesi",
Value: "sesi-budi-aktif",
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// Cookie persisten: bertahan 3600 detik di penyimpanan browser.
http.SetCookie(w, &http.Cookie{
Name: "preferensi",
Value: "tema-gelap",
Path: "/",
MaxAge: 3600,
})
// Prefix __Host-: wajib Secure, Path=/, tanpa Domain.
http.SetCookie(w, &http.Cookie{
Name: "__Host-tema",
Value: "biru",
Path: "/",
Secure: true,
})
// Cookie dengan batas waktu absolut lewat Expires.
http.SetCookie(w, &http.Cookie{
Name: "kupon",
Value: "DISKON20",
Path: "/",
Expires: time.Date(2026, 1, 16, 8, 0, 0, 0, time.UTC),
})
}
func bacaCookie(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("sesi")
if err != nil {
http.Error(w, "cookie sesi tidak ditemukan", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "nilai cookie sesi: %s", c.Value)
}
func namaSameSite(s http.SameSite) string {
switch s {
case http.SameSiteLaxMode:
return "Lax"
case http.SameSiteStrictMode:
return "Strict"
case http.SameSiteNoneMode:
return "None"
default:
return "default"
}
}
func deskripsi(c *http.Cookie) string {
maxAge := fmt.Sprintf("%d", c.MaxAge)
if c.MaxAge == 0 {
maxAge = "0 (cookie sesi)"
}
expires := c.Expires.Format("2006-01-02 15:04 MST")
if c.Expires.IsZero() {
expires = "tidak diset"
}
return fmt.Sprintf("HttpOnly=%-5v Secure=%-5v SameSite=%-7s Path=%s MaxAge=%s Expires=%s",
c.HttpOnly, c.Secure, namaSameSite(c.SameSite), c.Path, maxAge, expires)
}
func main() {
// Permintaan pertama: server mengatur empat cookie sekaligus.
req := httptest.NewRequest("GET", "/masuk", nil)
rec := httptest.NewRecorder()
aturCookie(rec, req)
resp := rec.Result()
fmt.Println("header Set-Cookie mentah dari respons:")
for _, v := range resp.Header["Set-Cookie"] {
fmt.Println(" " + v)
}
// Parsing kembali atribut setiap cookie dari respons yang sama.
fmt.Println()
fmt.Println("atribut hasil parsing setiap cookie:")
cookies := resp.Cookies()
for _, c := range cookies {
fmt.Printf(" %-12s %s\n", c.Name, deskripsi(c))
}
// Kunjungan kedua: browser mengirim semua cookie kembali ke server.
fmt.Println()
fmt.Println("kunjungan kedua, cookie yang terkirim balik:")
reqKedua := httptest.NewRequest("GET", "/baca", nil)
for _, c := range cookies {
fmt.Printf(" %-12s = %s\n", c.Name, c.Value)
reqKedua.AddCookie(c)
}
recKedua := httptest.NewRecorder()
bacaCookie(recKedua, reqKedua)
fmt.Println()
fmt.Println("handler /baca membalas :", recKedua.Body.String())
}