Documentation › JSON API › Secure cookies
If you select the Secure cookies option when generating your codebase, an internal/cookies package will be included. This provides helper functions for reading and writing cookies.
This package provides six functions:
Read() and Write() for regular cookies.ReadSigned() and WriteSigned() for signed (tamper-proof) cookies.ReadEncrypted() and WriteEncrypted() for encrypted (confidential and tamper-proof) cookies.The Write() function takes a http.Cookie instance and adds it to the HTTP response. Before doing so it base64-encodes the cookie value and makes sure that it doesn't exceed 4096 bytes.
You can use it like so:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Initialize a Go cookie with whatever settings you want.
cookie := http.Cookie{
Name: "exampleCookie",
Value: "Hello Zoë!",
Path: "/",
MaxAge: 3600,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
// Add the cookie to the HTTP response.
err := cookies.Write(w, cookie)
if err != nil {
app.serverError(w, r, err)
return
}
...
}
The Read() function reads back cookies that have been written with Write(). It retrieves a specific cookie from the request, base64-decodes its value, and returns it. If the cookie value is not valid base64, it will return a cookies.ErrInvalidValue error.
For example:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Read the cookie value and handle any errors.
value, err := cookies.Read(r, "exampleCookie")
if err != nil {
switch {
case errors.Is(err, http.ErrNoCookie):
app.badRequest(w, r, err)
case errors.Is(err, cookies.ErrInvalidValue):
app.badRequest(w, r, err)
default:
app.serverError(w, r, err)
}
return
}
fmt.Println("The cookie value is", value)
...
}
Signed cookies are authenticated using HMAC-256, meaning that when you read a signed cookie you can trust that it has not been tampered with.
To use signed cookies, you must provide a secret key. This should be a random 32-character string generated using a CSRNG, and passed to your application as a configuration setting.
If you're using command-line flags for configuration, you should pass it via the --cookie-secret-key flag. For example:
$ go run ./cmd/api --cookie-secret-key=heoCDWSgJ430OvzyoLNE9mVV9UJFpOWx
If you're using environment variables, set it in COOKIE_SECRET_KEY. For example:
$ export COOKIE_SECRET_KEY="heoCDWSgJ430OvzyoLNE9mVV9UJFpOWx"
To write a signed cookie, use WriteSigned() and pass the secret key as the final argument:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Initialize a Go cookie as normal.
cookie := http.Cookie{
Name: "exampleCookie",
Value: "Hello Zoë!",
}
// Add the signed cookie to the HTTP response.
err := cookies.WriteSigned(w, cookie, app.config.cookie.secretKey)
if err != nil {
app.serverError(w, r, err)
return
}
...
}
The ReadSigned() function reads back cookies that have been written with WriteSigned(). It verifies that the cookie name and value have not been changed, and returns the value. If the verification checks fail, then ReadSigned() will return a cookies.ErrInvalidValue error.
For example:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Read the signed cookie value and handle any errors.
value, err := cookies.ReadSigned(r, "exampleCookie", app.config.cookie.secretKey)
if err != nil {
switch {
case errors.Is(err, http.ErrNoCookie):
app.badRequest(w, r, err)
case errors.Is(err, cookies.ErrInvalidValue):
app.badRequest(w, r, err)
default:
app.serverError(w, r, err)
}
return
}
fmt.Println("The cookie value is", value)
...
}
Encrypted cookies are encrypted using AES-GCM, which both authenticates and encrypts the data. This means the cookie contents cannot be read by the client, and when you read it back you can trust that it hasn't been tampered with.
As with signed cookies, you must provide a secret key for encryption. This should be a random 32-character string generated using a CSRNG, and passed to your application as a configuration setting.
As before, if you're using command-line flags for configuration, you should pass the key in the --cookie-secret-key flag. For example:
$ go run ./cmd/api --cookie-secret-key=heoCDWSgJ430OvzyoLNE9mVV9UJFpOWx
If you are using environment variables for configuration, you should set the secret key in COOKIE_SECRET_KEY.
$ export COOKIE_SECRET_KEY="heoCDWSgJ430OvzyoLNE9mVV9UJFpOWx"
To write an encrypted cookie, use WriteEncrypted() and pass the secret key as the final argument:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Initialize a Go cookie as normal.
cookie := http.Cookie{
Name: "exampleCookie",
Value: "Hello Zoë!",
}
// Add the signed cookie to the HTTP response.
err := cookies.WriteEncrypted(w, cookie, app.config.cookie.secretKey)
if err != nil {
app.serverError(w, r, err)
return
}
...
}
The ReadEncrypted() function reads back cookies that have been written with WriteEncrypted(). It decrypts them, verifies that the cookie name and value have not been changed, and returns the value. If the decryption or verification checks fail, then ReadEncrypted() will return a cookies.ErrInvalidValue error.
For example:
func (app *application) exampleHandler(w http.ResponseWriter, r *http.Request) {
// Read the encrypted cookie value and handle any errors.
value, err := cookies.ReadEncrypted(r, "exampleCookie", app.config.cookie.secretKey)
if err != nil {
switch {
case errors.Is(err, http.ErrNoCookie):
app.badRequest(w, r, err)
case errors.Is(err, cookies.ErrInvalidValue):
app.badRequest(w, r, err)
default:
app.serverError(w, r, err)
}
return
}
fmt.Println("The cookie value is", value)
...
}
For more background and a deeper explanation of how these helpers work, you might like to read this blog post.