scs

package module
v2.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2020 License: MIT Imports: 15 Imported by: 1

README

SCS: HTTP Session Management for Go

GoDoc Build status Go report card Test coverage

Features

  • Automatic loading and saving of session data via middleware.
  • Choice of server-side session stores including PostgreSQL, MySQL, Redis, BadgerDB and BoltDB. Custom session stores are also supported.
  • Supports multiple sessions per request, 'flash' messages, session token regeneration, and idle and absolute session timeouts.
  • Easy to extend and customize. Communicate session tokens to/from clients in HTTP headers or request/response bodies.
  • Efficient design. Smaller, faster and uses less memory than gorilla/sessions.

Instructions

Installation

This package requires Go 1.12 or newer.

$ go get github.com/alexedwards/scs/v2

Note: If you're using the traditional GOPATH mechanism to manage dependencies, instead of modules, you'll need to go get and import github.com/alexedwards/scs without the v2 suffix.

Please use versioned releases. Code in tip may contain experimental features which are subject to change.

Basic Use

SCS implements a session management pattern following the OWASP security guidelines. Session data is stored on the server, and a randomly-generated unique session token (or session ID) is communicated to and from the client in a session cookie.

package main

import (
	"io"
	"net/http"
	"time"

	"github.com/alexedwards/scs/v2"
)

var sessionManager *scs.SessionManager

func main() {
	// Initialize a new session manager and configure the session lifetime.
	sessionManager = scs.New()
	sessionManager.Lifetime = 24 * time.Hour

	mux := http.NewServeMux()
	mux.HandleFunc("/put", putHandler)
	mux.HandleFunc("/get", getHandler)

	// Wrap your handlers with the LoadAndSave() middleware.
	http.ListenAndServe(":4000", sessionManager.LoadAndSave(mux))
}

func putHandler(w http.ResponseWriter, r *http.Request) {
	// Store a new key and value in the session data.
	sessionManager.Put(r.Context(), "message", "Hello from a session!")
}

func getHandler(w http.ResponseWriter, r *http.Request) {
	// Use the GetString helper to retrieve the string value associated with a
	// key. The zero value is returned if the key does not exist.
	msg := sessionManager.GetString(r.Context(), "message")
	io.WriteString(w, msg)
}
$ curl -i --cookie-jar cj --cookie cj localhost:4000/put
HTTP/1.1 200 OK
Cache-Control: no-cache="Set-Cookie"
Set-Cookie: session=lHqcPNiQp_5diPxumzOklsSdE-MJ7zyU6kjch1Ee0UM; Path=/; Expires=Sat, 27 Apr 2019 10:28:20 GMT; Max-Age=86400; HttpOnly; SameSite=Lax
Vary: Cookie
Date: Fri, 26 Apr 2019 10:28:19 GMT
Content-Length: 0

$ curl -i --cookie-jar cj --cookie cj localhost:4000/get
HTTP/1.1 200 OK
Date: Fri, 26 Apr 2019 10:28:24 GMT
Content-Length: 21
Content-Type: text/plain; charset=utf-8

Hello from a session!
Configuring Session Behavior

Session behavior can be configured via the SessionManager fields.

sessionManager = scs.New()
sessionManager.Lifetime = 3 * time.Hour
sessionManager.IdleTimeout = 20 * time.Minute
sessionManager.Cookie.Name = "session_id"
sessionManager.Cookie.Domain = "example.com"
sessionManager.Cookie.HttpOnly = true
sessionManager.Cookie.Path = "/example/"
sessionManager.Cookie.Persist = true
sessionManager.Cookie.SameSite = http.SameSiteStrictMode
sessionManager.Cookie.Secure = true

Documentation for all available settings and their default values can be found here.

Working with Session Data

Data can be set using the Put() method and retrieved with the Get() method. A variety of helper methods like GetString(), GetInt() and GetBytes() are included for common data types. Please see the documentation for a full list of helper methods.

The Pop() method (and accompanying helpers for common data types) act like a one-time Get(), retrieving the data and removing it from the session in one step. These are useful if you want to implement 'flash' message functionality in your application, where messages are displayed to the user once only.

Some other useful functions are Exists() (which returns a bool indicating whether or not a given key exists in the session data) and Keys() (which returns a sorted slice of keys in the session data).

Individual data items can be deleted from the session using the Remove() method. Alternatively, all session data can de deleted by using the Destroy() method. After calling Destroy(), any further operations in the same request cycle will result in a new session being created --- with a new session token and a new lifetime.

Behind the scenes SCS uses gob encoding to store session data, so if you want to store custom types in the session data they must be registered with the encoding/gob package first. Struct fields of custom types must also be exported so that they are visible to the encoding/gob package. Please see here for a working example.

Loading and Saving Sessions

Most applications will use the LoadAndSave() middleware. This middleware takes care of loading and committing session data to the session store, and communicating the session token to/from the client in a cookie as necessary.

If you want to customize the behavior (like communicating the session token to/from the client in a HTTP header, or creating a distributed lock on the session token for the duration of the request) you are encouraged to create your own alternative middleware using the code in LoadAndSave() as a template. An example is given here.

Or for more fine-grained control you can load and save sessions within your individual handlers (or from anywhere in your application). See here for an example.

Configuring the Session Store

By default SCS uses an in-memory store for session data. This is convenient (no setup!) and very fast, but all session data will be lost when your application is stopped or restarted. Therefore it's useful for applications where data loss is an acceptable trade off for fast performance, or for prototyping and testing purposes. In most production applications you will want to use a persistent session store like PostgreSQL or MySQL instead.

The session stores currently included are shown in the table below. Please click the links for usage instructions and examples.

Package
badgerstore BadgerDB based session store
boltstore BoltDB based session store
memstore In-memory session store (default)
mysqlstore MySQL based session store
postgresstore PostgreSQL based session store
redisstore Redis based session store
sqlite3store SQLite3 based session store

Custom session stores are also supported. Please see here for more information.

Using Custom Session Stores

scs.Store defines the interface for custom session stores. Any object that implements this interface can be set as the store when configuring the session.

type Store interface {
	// Delete should remove the session token and corresponding data from the
	// session store. If the token does not exist then Delete should be a no-op
	// and return nil (not an error).
	Delete(token string) (err error)

	// Find should return the data for a session token from the store. If the
	// session token is not found or is expired, the found return value should
	// be false (and the err return value should be nil). Similarly, tampered
	// or malformed tokens should result in a found return value of false and a
	// nil err value. The err return value should be used for system errors only.
	Find(token string) (b []byte, found bool, err error)

	// Commit should add the session token and data to the store, with the given
	// expiry time. If the session token already exists, then the data and
	// expiry time should be overwritten.
	Commit(token string, b []byte, expiry time.Time) (err error)
}
Preventing Session Fixation

To help prevent session fixation attacks you should renew the session token after any privilege level change. Commonly, this means that the session token must to be changed when a user logs in or out of your application. You can do this using the RenewToken() method like so:

func loginHandler(w http.ResponseWriter, r *http.Request) {
	userID := 123

	// First renew the session token...
	err := sessionManager.RenewToken(r.Context())
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// Then make the privilege-level change.
	sessionManager.Put(r.Context(), "userID", userID)
}
Multiple Sessions per Request

It is possible for an application to support multiple sessions per request, with different lifetime lengths and even different stores. Please see here for an example.

Compatibility

This package requires Go 1.12 or newer.

It is not compatible with the Echo framework. Please consider using the Echo session manager instead.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Codec

type Codec interface {
	Encode(deadline time.Time, values map[string]interface{}) ([]byte, error)
	Decode([]byte) (deadline time.Time, values map[string]interface{}, err error)
}

Codec is the interface for encoding/decoding session data to and from a byte slice for use by the session store.

type GobCodec

type GobCodec struct{}

GobCodec is used for encoding/decoding session data to and from a byte slice using the encoding/gob package.

func (GobCodec) Decode

func (GobCodec) Decode(b []byte) (time.Time, map[string]interface{}, error)

Decode converts a byte slice into a session deadline and values.

func (GobCodec) Encode

func (GobCodec) Encode(deadline time.Time, values map[string]interface{}) ([]byte, error)

Encode converts a session dealine and values into a byte slice.

type Session deprecated

type Session = SessionManager

Deprecated: Session is a backwards-compatible alias for SessionManager.

type SessionCookie

type SessionCookie struct {
	// Name sets the name of the session cookie. It should not contain
	// whitespace, commas, colons, semicolons, backslashes, the equals sign or
	// control characters as per RFC6265. The default cookie name is "session".
	// If your application uses two different sessions, you must make sure that
	// the cookie name for each is unique.
	Name string

	// Domain sets the 'Domain' attribute on the session cookie. By default
	// it will be set to the domain name that the cookie was issued from.
	Domain string

	// HttpOnly sets the 'HttpOnly' attribute on the session cookie. The
	// default value is true.
	HttpOnly bool

	// Path sets the 'Path' attribute on the session cookie. The default value
	// is "/". Passing the empty string "" will result in it being set to the
	// path that the cookie was issued from.
	Path string

	// Persist sets whether the session cookie should be persistent or not
	// (i.e. whether it should be retained after a user closes their browser).
	// The default value is true, which means that the session cookie will not
	// be destroyed when the user closes their browser and the appropriate
	// 'Expires' and 'MaxAge' values will be added to the session cookie.
	Persist bool

	// SameSite controls the value of the 'SameSite' attribute on the session
	// cookie. By default this is set to 'SameSite=Lax'. If you want no SameSite
	// attribute or value in the session cookie then you should set this to 0.
	SameSite http.SameSite

	// Secure sets the 'Secure' attribute on the session cookie. The default
	// value is false. It's recommended that you set this to true and serve all
	// requests over HTTPS in production environments.
	// See https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#transport-layer-security.
	Secure bool
}

SessionCookie contains the configuration settings for session cookies.

type SessionManager

type SessionManager struct {
	// IdleTimeout controls the maximum length of time a session can be inactive
	// before it expires. For example, some applications may wish to set this so
	// there is a timeout after 20 minutes of inactivity. By default IdleTimeout
	// is not set and there is no inactivity timeout.
	IdleTimeout time.Duration

	// Lifetime controls the maximum length of time that a session is valid for
	// before it expires. The lifetime is an 'absolute expiry' which is set when
	// the session is first created and does not change. The default value is 24
	// hours.
	Lifetime time.Duration

	// Store controls the session store where the session data is persisted.
	Store Store

	// Cookie contains the configuration settings for session cookies.
	Cookie SessionCookie

	// Codec controls the encoder/decoder used to transform session data to a
	// byte slice for use by the session store. By default session data is
	// encoded/decoded using encoding/gob.
	Codec Codec

	// ErrorFunc allows you to control behavior when an error is encountered by
	// the LoadAndSave middleware. The default behavior is for a HTTP 500
	// "Internal Server Error" message to be sent to the client and the error
	// logged using Go's standard logger. If a custom ErrorFunc is set, then
	// control will be passed to this instead. A typical use would be to provide
	// a function which logs the error and returns a customized HTML error page.
	ErrorFunc func(http.ResponseWriter, *http.Request, error)
	// contains filtered or unexported fields
}

SessionManager holds the configuration settings for your sessions.

func New

func New() *SessionManager

New returns a new session manager with the default options. It is safe for concurrent use.

func NewSession deprecated

func NewSession() *SessionManager

Deprecated: NewSession is a backwards-compatible alias for New. Use the New function instead.

func (*SessionManager) Clear

func (s *SessionManager) Clear(ctx context.Context) error

Clear removes all data for the current session. The session token and lifetime are unaffected. If there is no data in the current session this is a no-op.

func (*SessionManager) Commit

func (s *SessionManager) Commit(ctx context.Context) (string, time.Time, error)

Commit saves the session data to the session store and returns the session token and expiry time.

Most applications will use the LoadAndSave() middleware and will not need to use this method.

func (*SessionManager) Destroy

func (s *SessionManager) Destroy(ctx context.Context) error

Destroy deletes the session data from the session store and sets the session status to Destroyed. Any further operations in the same request cycle will result in a new session being created.

func (*SessionManager) Exists

func (s *SessionManager) Exists(ctx context.Context, key string) bool

Exists returns true if the given key is present in the session data.

func (*SessionManager) Get

func (s *SessionManager) Get(ctx context.Context, key string) interface{}

Get returns the value for a given key from the session data. The return value has the type interface{} so will usually need to be type asserted before you can use it. For example:

foo, ok := session.Get(r, "foo").(string)
if !ok {
	return errors.New("type assertion to string failed")
}

Also see the GetString(), GetInt(), GetBytes() and other helper methods which wrap the type conversion for common types.

func (*SessionManager) GetBool

func (s *SessionManager) GetBool(ctx context.Context, key string) bool

GetBool returns the bool value for a given key from the session data. The zero value for a bool (false) is returned if the key does not exist or the value could not be type asserted to a bool.

func (*SessionManager) GetBytes

func (s *SessionManager) GetBytes(ctx context.Context, key string) []byte

GetBytes returns the byte slice ([]byte) value for a given key from the session data. The zero value for a slice (nil) is returned if the key does not exist or could not be type asserted to []byte.

func (*SessionManager) GetFloat

func (s *SessionManager) GetFloat(ctx context.Context, key string) float64

GetFloat returns the float64 value for a given key from the session data. The zero value for an float64 (0) is returned if the key does not exist or the value could not be type asserted to a float64.

func (*SessionManager) GetInt

func (s *SessionManager) GetInt(ctx context.Context, key string) int

GetInt returns the int value for a given key from the session data. The zero value for an int (0) is returned if the key does not exist or the value could not be type asserted to an int.

func (*SessionManager) GetString

func (s *SessionManager) GetString(ctx context.Context, key string) string

GetString returns the string value for a given key from the session data. The zero value for a string ("") is returned if the key does not exist or the value could not be type asserted to a string.

func (*SessionManager) GetTime

func (s *SessionManager) GetTime(ctx context.Context, key string) time.Time

GetTime returns the time.Time value for a given key from the session data. The zero value for a time.Time object is returned if the key does not exist or the value could not be type asserted to a time.Time. This can be tested with the time.IsZero() method.

func (*SessionManager) Key

func (s *SessionManager) Key() contextKey

func (*SessionManager) Keys

func (s *SessionManager) Keys(ctx context.Context) []string

Keys returns a slice of all key names present in the session data, sorted alphabetically. If the data contains no data then an empty slice will be returned.

func (*SessionManager) Load

func (s *SessionManager) Load(ctx context.Context, token string) (context.Context, error)

Load retrieves the session data for the given token from the session store, and returns a new context.Context containing the session data. If no matching token is found then this will create a new session.

Most applications will use the LoadAndSave() middleware and will not need to use this method.

func (*SessionManager) LoadAndSave

func (s *SessionManager) LoadAndSave(next http.Handler) http.Handler

LoadAndSave provides middleware which automatically loads and saves session data for the current request, and communicates the session token to and from the client in a cookie.

func (*SessionManager) Pop

func (s *SessionManager) Pop(ctx context.Context, key string) interface{}

Pop acts like a one-time Get. It returns the value for a given key from the session data and deletes the key and value from the session data. The session data status will be set to Modified. The return value has the type interface{} so will usually need to be type asserted before you can use it.

func (*SessionManager) PopBool

func (s *SessionManager) PopBool(ctx context.Context, key string) bool

PopBool returns the bool value for a given key and then deletes it from the session data. The session data status will be set to Modified. The zero value for a bool (false) is returned if the key does not exist or the value could not be type asserted to a bool.

func (*SessionManager) PopBytes

func (s *SessionManager) PopBytes(ctx context.Context, key string) []byte

PopBytes returns the byte slice ([]byte) value for a given key and then deletes it from the from the session data. The session data status will be set to Modified. The zero value for a slice (nil) is returned if the key does not exist or could not be type asserted to []byte.

func (*SessionManager) PopFloat

func (s *SessionManager) PopFloat(ctx context.Context, key string) float64

PopFloat returns the float64 value for a given key and then deletes it from the session data. The session data status will be set to Modified. The zero value for an float64 (0) is returned if the key does not exist or the value could not be type asserted to a float64.

func (*SessionManager) PopInt

func (s *SessionManager) PopInt(ctx context.Context, key string) int

PopInt returns the int value for a given key and then deletes it from the session data. The session data status will be set to Modified. The zero value for an int (0) is returned if the key does not exist or the value could not be type asserted to an int.

func (*SessionManager) PopString

func (s *SessionManager) PopString(ctx context.Context, key string) string

PopString returns the string value for a given key and then deletes it from the session data. The session data status will be set to Modified. The zero value for a string ("") is returned if the key does not exist or the value could not be type asserted to a string.

func (*SessionManager) PopTime

func (s *SessionManager) PopTime(ctx context.Context, key string) time.Time

PopTime returns the time.Time value for a given key and then deletes it from the session data. The session data status will be set to Modified. The zero value for a time.Time object is returned if the key does not exist or the value could not be type asserted to a time.Time.

func (*SessionManager) Put

func (s *SessionManager) Put(ctx context.Context, key string, val interface{})

Put adds a key and corresponding value to the session data. Any existing value for the key will be replaced. The session data status will be set to Modified.

func (*SessionManager) Remove

func (s *SessionManager) Remove(ctx context.Context, key string)

Remove deletes the given key and corresponding value from the session data. The session data status will be set to Modified. If the key is not present this operation is a no-op.

func (*SessionManager) RenewToken

func (s *SessionManager) RenewToken(ctx context.Context) error

RenewToken updates the session data to have a new session token while retaining the current session data. The session lifetime is also reset and the session data status will be set to Modified.

The old session token and accompanying data are deleted from the session store.

To mitigate the risk of session fixation attacks, it's important that you call RenewToken before making any changes to privilege levels (e.g. login and logout operations). See https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Session_Management_Cheat_Sheet.md#renew-the-session-id-after-any-privilege-level-change for additional information.

func (*SessionManager) Status

func (s *SessionManager) Status(ctx context.Context) Status

Status returns the current status of the session data.

type Status

type Status int

Status represents the state of the session data during a request cycle.

const (
	// Unmodified indicates that the session data hasn't been changed in the
	// current request cycle.
	Unmodified Status = iota

	// Modified indicates that the session data has been changed in the current
	// request cycle.
	Modified

	// Destroyed indicates that the session data has been destroyed in the
	// current request cycle.
	Destroyed
)

type Store

type Store interface {
	// Delete should remove the session token and corresponding data from the
	// session store. If the token does not exist then Delete should be a no-op
	// and return nil (not an error).
	Delete(token string) (err error)

	// Find should return the data for a session token from the store. If the
	// session token is not found or is expired, the found return value should
	// be false (and the err return value should be nil). Similarly, tampered
	// or malformed tokens should result in a found return value of false and a
	// nil err value. The err return value should be used for system errors only.
	Find(token string) (b []byte, found bool, err error)

	// Commit should add the session token and data to the store, with the given
	// expiry time. If the session token already exists, then the data and
	// expiry time should be overwritten.
	Commit(token string, b []byte, expiry time.Time) (err error)
}

Store is the interface for session stores.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL