HTTP Session Management for Go

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, idle and absolute session timeouts, and 'remember me' functionality.
  • 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 (using the pq driver)
pgxstore PostgreSQL based session store (using the pgx driver)
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.

You may have some problems using this package with the Echo framework. If you do, then please consider using the Echo session manager instead.

Comments
  • Add Firestore support

    Add Firestore support

    I'd like to use Firestore as database for session management.

    I saw that Jens-Uwe already worked on Firestore support (see https://github.com/jum/scs/commit/dd23a3e6d1ff63ed70f5020efeea25d01dde50ad), but since there's no PR yet I thought we could track it in a separate issue until something is merged.

  • Echo compatibility

    Echo compatibility

    Well done @alexedwards on re-imagining scs. :1st_place_medal: This was a comprehensive rewrite. A number of items I appreciate:

    • Status
    • Efficient load/save with help from Status
    • Session and SessionCookie are global in scope
    • I also like that IdleTimeout and Lifetime were split from the old options.
    • go.mod added for go modules support

    For me, v2 solves a number of issues for which I had kept a permanent local fork of scs on my machine. (My fork was getting a bit dated too!)

    I've done some testing and so far so good. I had one issue with a private token but this example provides a powerful solution to my issue.

  • Improve storage to support exclusive locks

    Improve storage to support exclusive locks

    As I can see, the redis storage implementation doesn't support any locks. There's no documentation on mechanism to how to do this right. The current Store interface doesn't provide any locks interface even for custom implementations.

    So, I'd really like to see this change so one could implement their own locks (until someone makes some library).

    For example, there's only one implementation of locks I've found (a simple one): https://github.com/bsm/redis-lock

  • Provide example of using SCS with Echo

    Provide example of using SCS with Echo

    Should scs include a folder dedicated to middleware examples? It would be similar to stores but dedicated to middleware for scs.

    Or would it be better for scs to make reference to third party middleware implementers on its README?

    It's a classic question. Either direction works for me primarily b/c scs doesn't have many open issues. If scs was highly trafficked in terms of issues, I'd probably suggest breaking-out the middleware to third parties.

    I have some echo middleware to contribute - just let me know what you'd prefer. Others may find a landing spot for their ideas too. (see #16)

  • panic in getSessionDataFromContext makes this package hard to work with

    panic in getSessionDataFromContext makes this package hard to work with

    Hello,

    I was a happy user of the v1 of this package, and recently decided to upgrade to v2, however, I found some of the new behaviors extremely difficult to work with.

    One piece in particular was the fact that the package throws a panic if you try and load a session from a request that doesn't have a session attached: getSessionDataFromContext. Previously, I could attempt to load a session blindly, and based on the response from the function, I'd know if the request had a session attached. Now, I have to make absolutely sure that I've got the LoadAndSave middleware inserted all over the place.

    Panics thrown from userland are pretty unidiomatic in go, I feel, and the only way to really handle them is by doing the defer-recover dance, which is messy and error-prone. I'm likely going to migrate to another session-management library.

  • Feature Request: enumerate all sessions

    Feature Request: enumerate all sessions

    In our application, want to implement a feature that is to view all online users and terminate a user session. I tried to get all the sessions to complete this feature, but did not find any method.

    If using dbStore, I can get it directly from the database, but we use a memstore. I tried to read * MemStore directly, but his items field is private.

    Hope to add a interface that can enumerate all sessions, which is very useful.

  • Multiple Set-Cookie headers in response

    Multiple Set-Cookie headers in response

    Here is small example:

    package main
    
    import (
    	"net/http"
    
    	"github.com/alexedwards/scs"
    )
    
    var sessionManager = scs.NewCookieManager("u46IpCV9y5Vlur8YvODJEhgOY8m9JVE4")
    
    func main() {
    	mux := http.NewServeMux()
    	mux.HandleFunc("/auth", login)
    
    	http.ListenAndServe(":4000", sessionManager.Use(mux))
    }
    
    func login(w http.ResponseWriter, r *http.Request) {
    	session := sessionManager.Load(r)
            
            // authenticate user ...
    
    	err := session.RenewToken(w)
    	if err != nil {
    		http.Error(w, err.Error(), 500)
                    return
    	}
    
    	if err := session.PutInt(w, "userId", 1); err != nil {
    		http.Error(w, err.Error(), 500)
    		return
    	}
    
    	if err := session.PutBool(w, "isAdmin", true); err != nil {
    		http.Error(w, err.Error(), 500)
    		return
    	}
    }
    

    Here is /auth response:

    HTTP/1.1 200 OK
    Set-Cookie: session=rXoTm-uiZM8eYszKWKTbUP-D-SBV0Pdx8DyMpX7jL55yVcOwRxLROJSeDHeuW0iYifwVpUnEiXyhU_H-Vl-1-2LpnjHnOvx1TS1yYuoccQP6P56iEXyCzngQRt_UkHGG5Wva5-0; Path=/; HttpOnly
    Set-Cookie: session=6HjYzvYsALD_UvkBNlrheCKijlQDAAd2rMsWN_URq7uO12n2ng2t-7PYSmHSV8l3n0TKtb6_y0-DKuc--uikxCBJ-NuvR0a91vj7dauPu_TMrCGtfa1cOgLpr1R2MhTCDt4qUNwmBNEblJrViAiW; Path=/; HttpOnly
    Set-Cookie: session=ewJmYE3psbCYtX39Zgj-Utv5XTjCJ5SfbsO32daXPyOovU0y0O2OPQtv6QlL9Zv-yZ-XJuYqPvkZQ5tt_tjLqtpKdvohTOLAjLp7XO9yfVjV5rgtC6hG5b9W0Hb_8shOUdZdKZdM936IEAbaRkltlDOEYqBSbFcXoZIjJUKs; Path=/; HttpOnly
    Date: Thu, 26 Oct 2017 09:08:01 GMT
    Content-Length: 0
    Content-Type: text/plain; charset=utf-8
    

    As you can see there are three different Set-Cookie headers with the same cookie-name in response which is wrong according to RFC 6265:

    Servers SHOULD NOT include more than one Set-Cookie header field in the same response with the same cookie-name.

  • Implement pgxstore

    Implement pgxstore

    pgxstore is a session store for postgresql based on the github.com/jackc/pgx package. The implementation is almost identical to the existing postgresstore package, with the only difference being the postgresql driver.

  • go get github.com/alexedwards/scs/v2 fails

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

    go get github.com/alexedwards/scs/v2 package github.com/alexedwards/scs/v2: cannot find package "github.com/alexedwards/scs/v2" in any of: /usr/local/go/src/github.com/alexedwards/scs/v2 (from $GOROOT) /home/wise/go/src/github.com/alexedwards/scs/v2 (from $GOPATH)

    I'm moving a site I have in development to a new machine. I have the site working on another machine, I last set that one up maybe a week ago.

    What's the easiest way to use scs and postgresql?

  • Memcached support?

    Memcached support?

    Looking around for a new session manager, one which supports memcached.

    SCS looks interesting and is written for modern Go (unlike Gorilla), but doesn't (yet) support memcached.

    Any plans on memcached support? :smile:

  • Using scs with Echo

    Using scs with Echo

    Is there a way to use the middle ware provided by scs within echo? I try to do as below way, but failed. Can you give me some suggestion?

    package main
    
    import (
        "github.com/labstack/echo"
        "github.com/labstack/echo/engine/standard"
        "github.com/labstack/echo/middleware"
        "github.com/alexedwards/scs/session"
        "github.com/alexedwards/scs/engine/memstore"
        "net/http"
    )
    
    func main() {
        sessionManager := session.Manage(memstore.New(0))
        e := echo.New()
        e.Use(middleware.Logger())
        e.Use(middleware.Recover())
        e.Use(standard.WrapMiddleware(sessionManager))
            e.SetDebug(true)
    
        e.GET("/", func(c echo.Context) error {
            err := session.PutString(c.Request().(*standard.Request).Request, "username", "admin")
            if err != nil {
                c.Logger().Error("session.PutString:", err)
                return err
            }
    
            if msg, err := session.GetString(c.Request().(*standard.Request).Request, "username"); err != nil || len(msg) == 0 {
                c.Logger().Info("session.GetString:", msg)
            }
    
            return c.String(http.StatusOK, "Hello, World!")
        })
    
        e.Run(standard.New(":1323"))
    }
    
    
  • Add a method to modify the deadline of the sessionData

    Add a method to modify the deadline of the sessionData

    At the moment every session has a fixed lifetime which is set on the initialization of the session manager. There is no way to modify the deadline for an individual session after.

    It would be useful if a method can be exposed to set the expiry for cases when the deadline differs from the default or modified due to other conditions.

    func (s *SessionManager) Expire(ctx context.Context, expiry time.Time) {
    	sd := s.getSessionDataFromContext(ctx)
    
    	sd.mu.Lock()
    	defer sd.mu.Unlock()
    	sd.deadline = expiry
    	sd.status = Modified
    }
    

    Let me know if I should proceed with a PR

  • Prevent gorm from logging a warning when record is not found.

    Prevent gorm from logging a warning when record is not found.

    When using the First function, gorm will log that it failed to find a record. Since this case is valid in scs it makes no sense to log it. By using different syntax the same functionality is achieved but without the logging.

  • http.Flusher compatiability

    http.Flusher compatiability

    Hello,

    I want to implement Server Sent Events and am using this library: https://github.com/r3labs/sse The library expects the http.ResponseWriter to be able to cast to a http.Flusher for flushing.

    The http.Flusher documentation states:

    // The default HTTP/1.x and HTTP/2 ResponseWriter implementations
    // support Flusher, *but ResponseWriter wrappers may not*. Handlers
    // should always test for this ability at runtime.
    

    Your library is wrapping http.ResponseWriter and is breaking the library. Would it be possible for you to make the bufferedResponseWriter implement the http.Flusher?

    References: https://github.com/r3labs/sse/issues/130

Speak HTTP like a local. (the simple, intuitive HTTP console, golang version)

http-gonsole This is the Go port of the http-console. Speak HTTP like a local Talking to an HTTP server with curl can be fun, but most of the time it'

Jul 14, 2021
Http client call for golang http api calls

httpclient-call-go This library is used to make http calls to different API services Install Package go get

Oct 7, 2022
fhttp is a fork of net/http that provides an array of features pertaining to the fingerprint of the golang http client.

fhttp The f stands for flex. fhttp is a fork of net/http that provides an array of features pertaining to the fingerprint of the golang http client. T

Jan 1, 2023
NATS HTTP Round Tripper - This is a Golang http.RoundTripper that uses NATS as a transport.

This is a Golang http.RoundTripper that uses NATS as a transport. Included is a http.RoundTripper for clients, a server that uses normal HTTP Handlers and any existing http handler mux and a Caddy Server transport.

Dec 6, 2022
Simple HTTP package that wraps net/http

Simple HTTP package that wraps net/http

Jan 17, 2022
Http-conection - A simple example of how to establish a HTTP connection using Golang

A simple example of how to establish a HTTP connection using Golang

Feb 1, 2022
Full-featured, plugin-driven, extensible HTTP client toolkit for Go

gentleman Full-featured, plugin-driven, middleware-oriented toolkit to easily create rich, versatile and composable HTTP clients in Go. gentleman embr

Dec 23, 2022
An enhanced http client for Golang
An enhanced http client for Golang

go-http-client An enhanced http client for Golang Documentation on go.dev ?? This package provides you a http client package for your http requests. Y

Dec 23, 2022
An enhanced HTTP client for Go
An enhanced HTTP client for Go

Heimdall Description Installation Usage Making a simple GET request Creating a hystrix-like circuit breaker Creating a hystrix-like circuit breaker wi

Jan 9, 2023
Enriches the standard go http client with retry functionality.

httpRetry Enriches the standard go http client with retry functionality using a wrapper around the Roundtripper interface. The advantage of this libra

Dec 10, 2022
Go (golang) http calls with retries and backoff

pester pester wraps Go's standard lib http client to provide several options to increase resiliency in your request. If you experience poor network co

Dec 28, 2022
http client for golang
http client for golang

Request HTTP client for golang, Inspired by Javascript-axios Python-request. If you have experience about axios or requests, you will love it. No 3rd

Dec 18, 2022
Simple HTTP and REST client library for Go

Resty Simple HTTP and REST client library for Go (inspired by Ruby rest-client) Features section describes in detail about Resty capabilities Resty Co

Jan 1, 2023
A nicer interface for golang stdlib HTTP client

rq A nicer interface for golang stdlib HTTP client Documents rq: here client: here jar: here Why? Because golang HTTP client is a pain in the a... Fea

Dec 12, 2022
A Go HTTP client library for creating and sending API requests
A Go HTTP client library for creating and sending API requests

Sling Sling is a Go HTTP client library for creating and sending API requests. Slings store HTTP Request properties to simplify sending requests and d

Jan 7, 2023
HTTP Load Testing And Benchmarking Tool

GBench HTTP Load Testing And Benchmarking Tool inspired by Apache Benchmark and Siege. Requirements You need Golang installed and ready on your system

Jan 2, 2020
HTTP/HTTPS load testing and benchmarking tool

Introduction I wrote that code because: (the obvious reason::I love to write code in Go) We are working so hard to optimize our servers - why shouldn'

Dec 5, 2022
An HTTP proxy library for Go

Introduction Package goproxy provides a customizable HTTP proxy library for Go (golang), It supports regular HTTP proxy, HTTPS through CONNECT, and "h

Jan 4, 2023
Useful HTTP middlewares

This project contains middlewares that I often found myself reimplementing in new projects. In addition, it includes a middleware that logs in a forma

Apr 16, 2022