Sessions, flash & CSRF

Secure-cookie sessions, one-shot flash messages, and CSRF protection.

Sessions

go-bananas uses gorilla/sessions for storage. The cookiestore package provides a secure-cookie store whose HMAC and encryption keys are hot-reloadable: they come from an EntropyFunc consulted on every request, so keys can be rotated (or sourced from a secret/key manager) without a restart.

store := cookiestore.New(entropy, &sessions.Options{
	Path:     "/",
	MaxAge:   3600,
	HttpOnly: true,
	Secure:   true,
})

Install RequireSession to load and save the session per request:

r.Use(middleware.RequireSession(store, nil, h))

Read and write typed values through the session package (CSRFToken, LastActivity, Nonce, Region, and Flash), all of which are nil-safe.

Idle timeout

CheckSessionIdleNoAuth enforces an idle timeout on routes that have no other auth check (the example app wires a 30-minute timeout). You supply the handler to run when the session is idle:

r.Use(middleware.CheckSessionIdleNoAuth(30*time.Minute, func(w http.ResponseWriter, req *http.Request) {
	response.SeeOther(w, req, "/login")
}))

Flash messages

Flash messages are stored on the session, surfaced once on the next render, then automatically cleared. They survive a redirect, making the post/redirect/get pattern clean:

func submit(w http.ResponseWriter, r *http.Request) {
	flash := session.Flash(webctx.SessionFromContext(r.Context()))
	flash.Alert("Saved!")             // also Error, Warning
	response.SeeOther(w, r, "/")      // redirect; the flash shows on the next page
}

In a template:

{{ range .flash.Alerts }}<div class="flash">{{ . }}</div>{{ end }}

Duplicate messages are de-duplicated, so adding the same text twice shows it once.

CSRF

Install HandleCSRF after RequireSession. It stores a per-session token, exposes masked helpers on the template map, and verifies the token on mutating requests (anything other than GET/HEAD/OPTIONS/TRACE):

r.Use(middleware.HandleCSRF(h))

In your forms, include the hidden field; for JavaScript, read the meta tag:

<form method="POST" action="/submit">
  {{ .csrfField }}
  <button>Submit</button>
</form>

{{ .csrfMeta }}  <!-- <meta name="csrf-token" content="…"> -->

A POST without a valid token receives a 401. The token is masked with a random pad on every render to defeat BREACH-style attacks.