❯ go-clikit
v0.10.0 pkg.go.dev ↗

packages › local web › localweb/session

session

import "github.com/GSI-HPC/go-clikit/localweb/session"

Let in only the browser that opened the printed address. Exchange the launch token for a session cookie, and keep the CSRF token.

since v0.8.0 imports: localweb decisions 34
The place of localweb/session in go-clikit: the packages it imports, and the packages that import it.termtextprogressdisplaycliprogressprogresstestmcpprogresseventlogfanoutfanouttestclioutdoctorfsutilcobratreecobratreetestlocalwebsessionsselocalwebtestlaunchassetspagehtmx

The address that a program prints carries a launch token. The first navigation exchanges it for a session cookie. Each session has a CSRF token, which each request that can change something sends back.

http://127.0.0.1:8421/?token=…Set-Cookie: …_8421HttpOnly; SameSite=StrictPOST + CSRF token
A form or a script sends the CSRF token back with each request that can change something.
token := "LAUNCHTOKEN" // session.NewToken() in a program
sessions, err := session.New(session.Options{
	Token: token, CookiePrefix: "prog_ui_", CSRFHeader: "X-Prog-CSRF",
	Refuse: func(w http.ResponseWriter, r *http.Request, f localweb.Refusal) {
		f.Message += "\nOpen the address that prog ui printed in your terminal, or run prog ui again."
		localweb.Refuse(w, r, f)
	},
})
if err != nil {
	fmt.Println(err)
	return
}
page := template.Must(template.New("page").Parse(
	`<body hx-headers='{"X-Prog-CSRF": "{{.}}"}'><form method="post"><input type="hidden" name="csrf" value="{{.}}">`))
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
	_ = page.Execute(w, session.CSRF(r.Context()))
})
h := sessions.Middleware(mux)

for _, target := range []string{"/?token=" + token, "/?token=OLD", "/"} {
	w := httptest.NewRecorder()
	r := httptest.NewRequest(http.MethodGet, target, nil)
	r.Host = "127.0.0.1:7374"
	h.ServeHTTP(w, r)
	if w.Code == http.StatusSeeOther {
		fmt.Println(w.Code, "to", w.Header().Get("Location"), "with the cookie", w.Result().Cookies()[0].Name)
	} else {
		fmt.Print(w.Code, " ", w.Body)
	}
}
303 to / with the cookie prog_ui_7374
403 This link holds an old or wrong token.
Open the address that prog ui printed in your terminal, or run prog ui again.
403 This browser is not signed in.
Open the address that prog ui printed in your terminal, or run prog ui again.
ExampleManager_Middleware in localweb/session/example_test.go. CI runs it and checks its output.
  • HttpOnly, so no script reads it.
  • SameSite=Strict, so a browser leaves it out of a request that another site starts.
  • Its name ends in the port of the Host, because a browser keeps cookies by host name and not by port. Two servers on one machine each keep their own session.
  • No Secure attribute, which a browser refuses over the plain HTTP of a loopback listener, and no lifetime.

The names and texts that this package sends are an interface (decision 34).