Categorygithub.com/stackus/sessions
modulepackage
0.2.0
Repository: https://github.com/stackus/sessions.git
Documentation: pkg.go.dev

# README

Sessions

A type-safe, secure cookie manager for Go. Support for custom storage and encryption backends.

Go Reference Go Report Card Coverage Status Test Status

Features

  • Type-safe session data: the session data is stored in a type that you define.
  • Simple API: use it as an easy way to set signed (and optionally encrypted) cookies.
  • Built-in backends to store sessions in cookies or the filesystem.
  • Flash messages: messages that persist only for the current request, until the next request, or until read or removed.
  • Convenient way to switch session persistency (aka "remember me") and set other attributes.
  • Mechanism to rotate authentication and encryption keys.
  • Multiple sessions per request, even using different backends.
  • Interfaces and infrastructure for custom session backends: sessions from different stores can be retrieved and batch-saved using a common API.
  • Easy initialization of complex session data structures.

Requirements

  • Go 1.23+

Genesis

This project was created while the original gorilla repos were being archived and their future was unknown. During that time I grabbed both gorilla/sessions and gorilla/securecookie and mashed them together into a new codebase. I made changes here and there and eventually ended up with a new external API with a lot of the original code still intact.

Functionally, a lot of what this project does is the same as the original gorilla code. The biggest changes are the changed API of the library, type-safe session data, and the two projects being merged into one.

Example Usage

package main

import (
	"github.com/stackus/sessions"
)

// create a type to hold the session data
type SessionData struct {
	sessions.Flash
	UserID  int
	Scopes  []string
	IsAdmin bool
}

const myHashKey = []byte("it's-a-secret-to-everybody.")

func main() {
	// create a store; the CookieStore will save the session data in a cookie
	store := sessions.NewCookieStore()
	
	// create a Codec to encode and decode the session data; Codecs have a lot 
	// of options such as changing the Serializer, adding encryption for extra 
	// security, etc. These options can be passed in as variadic arguments
	codec := sessions.NewCodec(myHashKey)
	
	// create the cookie options that will dictate how the cookie is saved by the browsers
	cookieOptions := sessions.NewCookieOptions()
	cookieOptions.Name = "my-session"
	cookieOptions.MaxAge = 3600 // 1 hour
	
	// create a new session manager for SessionData and with the cookieOptions, store, and 
	// one or more codecs
	sessionManager := sessions.NewSessionManager[SessionData](cookieOptions, store, codec)
	
	// later in an HTTP handler get the session for the request; if it doesn't exist, a 
	// new session is initialized and can be checked with the `IsNew` value
	session, _ := sessionManager.Get(r)
	
	// access the session data directly and with type safety
	session.Values.UserID = 1
	session.Values.Scopes = []string{"read", "write"}
	// use the embedded Flash type to add flash messages to the session
	session.Values.Add("success", "You have successfully logged in!")
	
	// save the session data
	_ = session.Save(w, r)
	
	// Redirect or render the response
	http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
}

Stores

Two stores are available out of the box: CookieStore and FileSystemStore.

CookieStore

store := sessions.NewCookieStore()

The CookieStore saves the session data in a cookie. This is particularly useful when you want horizontal scalability and don't want to store the session data on the server or add additional infrastructure to manage the session data. I highly recommend using encryption in the Codecs when using the CookieStore.

FileSystemStore

store := sessions.NewFileSystemStore(rootPathForSessions, maxFileSize)

The FileSystemStore saves the session data in a file on the server's filesystem. If you are using a single server and do not want to store the session data in a cookie, then this might be a good option for you.

Additional Stores

Additional stores can be created by implementing the Store interface.

type Store interface {
	Get(ctx context.Context, proxy *SessionProxy, cookieValue string) error
	New(ctx context.Context, proxy *SessionProxy) error
	Save(ctx context.Context, proxy *SessionProxy) error
}

Codecs

codec := sessions.NewCodec(hashKey, options...)

The Codec is responsible for encoding and decoding the session data as well as optionally encrypting and decrypting the data.

All Codecs require a HashKey which will be used to authenticate the session data using HMAC. Additional options can be passed in as variadic arguments to the NewCodec function to change the default behavior of the Codec.

NewCodec Options

  • WithMaxAge: sets the maximum age of the session cookie, defaults to 30 days
  • WithMinAge: sets the minimum age of the session cookie, defaults to 0
  • WithMaxLength: sets the maximum length of the encoded session cookie value, defaults to 4096
  • WithHashFn: sets the hash function used by the codec, defaults to sha256.New
  • WithBlockKey: sets the block key used by the codec; aes.NewCipher is used to create the block cipher
  • WithBlock: sets the block cipher used by the codec, defaults to aes.NewCipher
  • WithSerializer: sets the serializer used by the codec, defaults to sessions.JsonSerializer

Sessions

You may use whatever data structure you like for the session data.

Flash Messages

To use flash messages, you can include the Flash type into your session data type.

type SessionData struct {
	sessions.Flash
}

The Flash type can also be used as the session value directly. This is useful when you want to use flash messages and are using multiple session types in a single request.

The Flash type has the following methods:

  • Add(key string, message string): adds a flash message to the session that is available until the next request
  • Now(key string, message string): adds a flash message to the session that is available for the current request
  • Keep(key string, message string): adds a flash message to the session that is available until it is read or removed
  • Get(key string) string: gets and removes a flash message from the session
  • Remove(key string): removes a flash message from the session
  • Clear(): removes all flash messages from the session

Session Initialization

Complex types are not going to be a problem. If your are using a type which need to be initialized, then you only need to add a Init() method to your type.

type SessionData struct {
	MapData map[string]string
}

func (s *SessionData) Init() {
	s.MapData = make(map[string]string)
}

Use the Init() method to initialize maps, types that use pointers, or any other type that needs to be initialized.

The Init() method will be called when a new session is created and the session data is initialized.

SessionManager

sessionManager := sessions.NewSessionManager[SessionData](cookieOptions, store, codec)

The SessionManager is responsible for managing the session data for a specific type. The SessionManager requires a CookieOptions, a Store, and one or more Codecs.

Multiple Types Of Sessions

You will need to configure a different SessionManager for each type of session data you want to manage. A common pattern is to create a cookie for "Access," and then one for "Refresh" tokens.

accessManager := sessions.NewSessionManager[AccessData](cookieOptions, store, codec)
refreshManager := sessions.NewSessionManager[RefreshData](cookieOptions, store, codec)

You can reuse the same CookieOptions, Store, and Codec for each SessionManager if you'd like, but you can also configure them differently.

CookieOptions

cookieOptions := sessions.NewCookieOptions()

The CookieOptions dictate how the session cookie is saved by the browser. The CookieOptions have the following fields:

  • Path: the path of the cookie, defaults to "/"
  • Domain: the domain of the cookie, defaults to ""
  • MaxAge: the maximum age of the cookie in seconds, defaults to 30 days
  • Secure: whether the cookie should only be sent over HTTPS, defaults to false
  • HttpOnly: whether the cookie should be accessible only through HTTP, defaults to true
  • Partitioned: whether the cookie should be partitioned, defaults to false
  • SameSite: the SameSite attribute of the cookie, defaults to SameSiteLaxMode

Paritioned is a relatively new attribute that is not yet widely supported by browsers and will require Go 1.23+ to use.

For more information: https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies

Getting a Session

session, _ := sessionManager.Get(r, "__Host-my-session")

The Get function will return a session of type *Session[T], where T is the type provided to the SessionManager, from the given request and the matching cookie name. If the session does not exist, a new session will be initialized by the Store that is associated with the SessionManager.

Key Rotation

Key rotation is a critical part of securing your session data. By providing multiple Codecs to the SessionManager, you can rotate the keys used to encode and decode the session data.

codec1 := sessions.NewCodec(hashKey1)
codec2 := sessions.NewCodec(hashKey2)
sessionManager := sessions.NewSessionManager[SessionData](cookieOptions, store, codec1, codec2)

This way you can still decode session data encoded with the old key while encoding new session data with the new key. The BlockKey and Serializer can also be changed between Codecs to provide additional security and flexibility.

Session

The Session type is a wrapper around the session data and provides a type-safe way to access and save the session data. You can access the session data directly through the Values field.

type SessionData struct {
	UserID  int
	Scopes  []string
	IsAdmin bool
}

// Values is a SessionData type
session.Values.UserID = 1
session.Values.Scopes = []string{"read", "write"}

Saving a Session

err = session.Save(w, r)

The Save function will save the session data to the Store and set the session cookie in the response writer. This will write the session data to the Store and set the session cookie in the response even if the session data has not changed.

Deleting a Session

err = session.Delete(w, r) // cookie will be set to expire immediately
// OR
session.Expire() // do this anywhere you do not have access to the response writer
session.Save(w, r) // cookie will be deleted

Session Cookie and Store Persistence

The session will inherit the CookieOptions from the SessionManager, but there may be times when you want to change whether the session cookie is persistent or not. For example, if you have added a "Remember Me" feature to your application.

Two methods exist on the session to help with overriding the MaxAge set in the CookieOptions for the SessionManager:

  • Persist(maxAge int): alters the session instance to set the session cookie to be persisted for the provided maxAge in seconds
  • DoNotPersist(): alters the session instance to set the session cookie to be deleted when the browser is closed
if rememberMe {
	session.Persist(86400) // 1 day
} else {
	session.DoNotPersist()
}

Saving all Sessions

err = sessions.Save(w, r)

The Save function will save all sessions in the request context. This is useful when you have multiple sessions in a single request. All sessions will be saved even if the session data has not changed.

Information for Store Implementors

Implementing a new Store is relatively simple. The Store interface has three methods: Get, New, and Save.

type Store interface {
	Get(ctx context.Context, proxy *SessionProxy, cookieValue string) error
	New(ctx context.Context, proxy *SessionProxy) error
	Save(ctx context.Context, proxy *SessionProxy) error
}

Get

The Get method is responsible for retrieving the session data from the store. Unlike in the original gorilla/sessions, the Get method is only called after the session data has been loaded from the cookie. The cookie value is passed in exactly as it was received from the request. You should Decode the cookie value into the proxy.ID or proxy.Values fields.

func (s *MyStore) Get(ctx context.Context, proxy *SessionProxy, cookieValue string) error {
	// decode the cookie value into the proxy.ID or proxy.Values
	err := s.Codec.Decode([]byte(cookieValue), &proxy.ID)
	// now you've got the ID of the record or row that your store can then use to get the session data
}

New

The New method is responsible for initializing a new session. This method is called when a cookie is not found in the request.

Save

The Save method is responsible for saving the session data to the store and setting the session cookie in the response writer.

SessionProxy

The SessionProxy is a helper type that provides access to the session data and session lifecycle methods.

Fields:

  • ID string: The store should use this to keep track of the session data record or row.
  • Values any: This will be what holds or will hold the session data. It is recommended to only interact with this field with either the Encode or Decode methods.
  • IsNew bool: This will be true if the session is new, meaning it was created during this request and the stores New method was called.

Methods:

  • Decode(data []byte, dst any) error: decodes the session data into the provided destination such as the proxy.ID or proxy.Values. The Codecs that were provided to the SessionManager will be used during the decoding process.
  • Encode(src any) ([]byte, error): encodes the provided source such as the proxy.ID or proxy.Values into a byte slice. The Codecs that were provided to the SessionManager will be used during the encoding process.
  • Save(value string) error: write the session cookie to the response writer with the provided value as the cookie value. The MaxAge in the cookie options will be used to determine if the cookie should be deleted or not. It is recommended to call this method or Delete from inside the stores Save method.
  • Delete() error: delete the session cookie from the response writer.
  • IsExpired() bool: returns true if the session cookie is expired.
  • MaxAge() int: returns the maximum age of the session cookie.

License

This project is licensed under the BSD 3-Clause License — see the LICENSE file for details.

Gorilla/Sessions and Gorilla/SecureCookie licenses are included.

# Functions

NewCodec returns a new Codec set up with the hash key, optionally configured with additional provided CodecOption options.
NewCookieOptions returns a new CookieOptions with default values.
No description provided by the author
No description provided by the author
No description provided by the author
Save saves all sessions in the registry for the provided request.
WithBlock sets the block cipher used by the codec.
WithBlockKey sets the block key used by the codec.
WithHashFn sets the hash function used by the codec during the steps where a HMAC is calculated.
WithMaxAge sets the maximum age of the session cookie.
WithMaxLength sets the maximum length of the session cookie.
WithMinAge sets the minimum age of the session cookie.
WithSerializer sets the serializer used by the codec.

# Variables

No description provided by the author
Codec Defaults.
No description provided by the author
30 days.
No description provided by the author
No description provided by the author
Session Defaults.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
Flash is used to store messages for the current and next request Three types of messages can be stored: - Current request only: Add these messages with the Now(key, message) method.
GobSerializer is a serializer that uses the encoding/gob package to serialize and deserialize session values.
JsonSerializer is a serializer that uses the encoding/json package to serialize and deserialize session values.
No description provided by the author
No description provided by the author
No description provided by the author

# Interfaces

No description provided by the author
CodecOption is an option for configuring a codec.
Serializer is an interface for encoding and decoding session values.
No description provided by the author
No description provided by the author

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author