Start learning free

LESSON 92 OF 173 · BACKEND: USERS AND SECURITY

Sessions with Cookies in Go

Practice

A cookie is a small piece of data a site gives the browser with an answer. The browser keeps it and sends it back, on its own, with every later request to that site. That's how a website stays logged in without adding an Authorization header to each request.

In Go, http.SetCookie(w, &http.Cookie{…}) adds a cookie to the answer, and r.Cookie("name") reads one from the request. r.Cookie also gives an error, which is not nil when the request has no such cookie. For example, two routes could remember a shopper's favorite color:

app.HandleFunc("POST /color", func(w http.ResponseWriter, r *http.Request) {
	var data map[string]string
	json.NewDecoder(r.Body).Decode(&data)
	http.SetCookie(w, &http.Cookie{Name: "color", Value: data["color"], Path: "/"})
	writeJSON(w, 200, map[string]string{"color": data["color"]})
})
app.HandleFunc("GET /color", func(w http.ResponseWriter, r *http.Request) {
	color := "blue"
	cookie, err := r.Cookie("color")
	if err == nil {
		color = cookie.Value
	}
	writeJSON(w, 200, map[string]string{"color": color})
})

In this lesson, /login also sends the token in a cookie named "session", with HttpOnly: true so the page's scripts can't read it. currentUser uses that cookie when a request brings no Authorization header. When the header is there, it still wins. A made-up cookie value gets nowhere: no row in the tokens table has it. The tests use one client that keeps its cookies, like a browser:

GET /me  (no header, no cookie yet)  →  401  {"error": "Login required"}
POST /login  {"email": "ana@example.com", "password": "secret123"}  →  200  {"token": "…"}
GET /me  (no header)  →  200  {"email": "ana@example.com", "id": 2, "role": "customer"}
GET /welcome  (no header)  →  200  Welcome back, ana@example.com!
POST /products  {"name": "Desk", "price": 120}  (no header)  →  403  {"error": "Admins only"}
POST /login  {"email": "admin@example.com", "password": "admin-pass-123"}  →  200  {"token": "…"}
POST /products  {"name": "Desk", "price": 120}  (no header)  →  201  {"id": 9, "name": "Desk", "price": 120}
GET /me  (Bearer Ana's token)  →  200  {"email": "ana@example.com", "id": 2, "role": "customer"}
GET /me  (no header)  →  200  {"email": "admin@example.com", "id": 1, "role": "admin"}

Your task: at login, send the cookie "session" holding the token with http.SetCookie (with Path: "/" and HttpOnly: true), and let currentUser take the token from that cookie when the header gives none.


Assignment
Follow the Coding Tutorial to keep users logged in with a session cookie.


Hint
Without the header, strings.ReplaceAll(header, "Bearer ", "") gives the empty text "": that's when to use the cookie. Check err == nil too: with no cookie, cookie is nil, and reading cookie.Value would crash. Go refuses variables that are declared and never used, so the app only compiles again once cookie.Value is used.


STEP 1 OF 4
    Ctrl + Enter to check

    Auth Sessions in Go

    Keep going with Go1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free