session

package module
v1.0.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 25 Imported by: 0

README

session

Go Reference

Typed HTTP sessions for Go. Session data normally lives in a server-side KV store, with encrypted cookie sessions available when needed.

go get lds.li/session
type SessionData struct {
	UserID string `json:"user_id"`
}

sessions, err := session.NewKVManager[SessionData](session.NewMemoryKV(), nil)
if err != nil {
	log.Fatal(err)
}

mux := http.NewServeMux()
mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
	sess := sessions.FromContext(r.Context())
	sess.Reset()
	sess.Set(SessionData{UserID: "123"})
})
mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
	sessions.FromContext(r.Context()).Delete()
})

handler := sessions.Wrap(mux)

Call Reset before storing an authenticated identity so a previously issued session ID cannot be reused after login. Reset also restarts the session lifetime.

By default a session lives 24 hours from creation (MaxLifetime). It is a session cookie (Persist off), the browser will drop it when the process ends. Persist requires MaxLifetime, in this case the cookie will be stored and last between browser processes. Set IdleTimeout if you want sliding expiry on session access.

NewMemoryKV is useful for tests and single-process development. Production deployments normally provide a shared KV implementation. The sqlkv package provides one for database/sql.

Sessions use JSON encoding by default. Set Codec: session.GobCodec{} to use gob instead. All instances sharing a store must use the same codec, and changing it invalidates existing sessions.

Cookie-backed sessions are available when self-contained storage is useful:

aead, err := session.NewRotatingAESGCM(currentKey, previousKey)
if err != nil {
	log.Fatal(err)
}
cookieSessions, err := session.NewCookieManager[SessionData](aead, nil)
if err != nil {
	log.Fatal(err)
}

Cookie sessions are self-contained. The server can expire the browser's copy, but it cannot revoke a copied cookie before its expiry. Load keys from a secret store and keep the current key first. NewRotatingAESGCM and NewHMACSHA256Authenticator both accept older keys for rotation.

Session changes must happen before the response is written or flushed. A mutation after the response has committed panics, since it is too late to send the updated cookie.

Testing

The sessiontest package attaches a session directly to a request without running storage or cookie middleware:

req, change := sessiontest.WithSession(t, req, sessions, SessionData{UserID: "123"})
handler.ServeHTTP(recorder, req)

if !change.Saved() {
	t.Fatal("handler did not update the session")
}

Options are available for new sessions and initial flash messages.

DBSC

KV-backed sessions can be bound to a device using Device Bound Session Credentials:

sessions, err := session.NewKVManager[SessionData](store, &session.KVManagerOpts[SessionData]{
	DBSCRefreshInterval:  10 * time.Minute,
	DBSCOrigin:           "https://example.com",
	DBSCRegistrationPath: "/dbsc/register",
	DBSCRefreshPath:      "/dbsc/refresh",
})

The manager serves the registration and refresh endpoints and enforces proofs on protected sessions. The implementation follows the current W3C Editor's Draft and has end-to-end coverage against Chrome.

Documentation

Overview

Package session provides typed HTTP sessions backed by encrypted cookies or a server-side key-value store.

A Manager installs a request-scoped Session in the request context. Session mutations are saved before the response is committed; mutating after a Write, WriteHeader, or Flush panics.

Cookie-backed sessions cannot revoke previously issued cookie values. Use a KV-backed manager when logout or session renewal must invalidate an old credential server-side.

Index

Examples

Constants

View Source
const DefaultMaxLifetime = 24 * time.Hour

DefaultMaxLifetime is used when neither manager lifetime option is set.

Variables

View Source
var ErrAuthenticationFailed = errors.New("authentication failed")

ErrAuthenticationFailed is returned when an authenticator does not match.

Functions

func NewRotatingAESGCM

func NewRotatingAESGCM(currentKey []byte, previousKeys ...[]byte) (cipher.AEAD, error)

NewRotatingAESGCM creates a rotation-aware AES-GCM cipher. Each key must be 16, 24, or 32 bytes. Prefer 32 random bytes for AES-256-GCM. Seal uses currentKey; Open also tries previousKeys in order. The keys are copied.

Types

type Authenticator

type Authenticator interface {
	// Authenticate returns an authenticator for message. The caller owns the
	// returned byte slice.
	Authenticate(message []byte) ([]byte, error)
	// Verify returns nil when authenticator is valid for message.
	Verify(message, authenticator []byte) error
}

Authenticator authenticates opaque values such as session IDs. Implementations must be safe for concurrent use.

func NewHMACSHA256Authenticator

func NewHMACSHA256Authenticator(currentKey []byte, previousKeys ...[]byte) (Authenticator, error)

NewHMACSHA256Authenticator creates a rotation-aware authenticator. Every key must contain at least 32 bytes of cryptographically random data.

type Codec

type Codec interface {
	// contains filtered or unexported methods
}

Codec selects the encoding used for persisted sessions. The supported implementations are JSONCodec and GobCodec. All managers sharing a store must use the same codec; changing it invalidates existing sessions.

Codec is sealed so the persistence envelope can evolve without exposing it as public API.

type CookieManagerOpts

type CookieManagerOpts[T any] struct {
	// MaxLifetime is the maximum duration a session remains valid after its
	// creation. Zero disables the limit; negative values are rejected. When both
	// this and IdleTimeout are zero, DefaultMaxLifetime is used.
	MaxLifetime time.Duration
	// IdleTimeout is extended whenever a loaded session is used. Zero disables
	// the limit; negative values are rejected.
	IdleTimeout time.Duration
	// ErrorHandler handles failures that prevent the manager from safely loading
	// or persisting session state. When nil, the manager logs the error and
	// responds 500.
	ErrorHandler func(http.ResponseWriter, *http.Request, error)
	// Onload is called when a session is retrieved from storage. The returned
	// data replaces the in-memory value for this request and is not persisted
	// unless the handler also Set. Returning false deletes the session as if
	// Delete were called.
	Onload func(T) (T, bool)
	// Cookie settings.
	CookieOpts *SessionCookieOpts
	// Codec selects the persisted session encoding. Nil uses JSONCodec.
	Codec Codec
}

CookieManagerOpts configures options specifically for the cookie-based session manager

type Flash

type Flash struct {
	// Level controls how the application presents the message.
	Level FlashLevel `json:"level,omitempty"`
	// Message is the content presented to the user.
	Message string `json:"message"`
}

Flash is a message that remains in the session until consumed by TakeFlashes.

type FlashLevel

type FlashLevel string

FlashLevel identifies how an application should present a flash message. Applications may define additional levels.

const (
	// FlashLevelInfo identifies an informational flash.
	FlashLevelInfo FlashLevel = "info"
	// FlashLevelError identifies an error flash.
	FlashLevelError FlashLevel = "error"
)

type GobCodec

type GobCodec struct{}

GobCodec selects Go gob session encoding.

type JSONCodec

type JSONCodec struct{}

JSONCodec selects JSON session encoding. It is the default.

type KV

type KV interface {
	Get(_ context.Context, key string) (_ []byte, found bool, _ error)
	Set(_ context.Context, key string, expiresAt time.Time, value []byte) error
	Delete(_ context.Context, key string) error
}

KV stores encoded server-side sessions. Implementations must be safe for concurrent use, must not retain the value passed to Set, and must return a caller-owned value from Get. Entries at or past expiresAt must be reported as not found.

func NewMemoryKV

func NewMemoryKV() KV

NewMemoryKV returns a concurrency-safe in-memory KV store. Its contents are local to the process and are lost when the process exits.

type KVManagerOpts

type KVManagerOpts[T any] struct {
	// MaxLifetime is the maximum duration a session remains valid after its
	// creation. Zero disables the limit; negative values are rejected. When both
	// this and IdleTimeout are zero, DefaultMaxLifetime is used.
	MaxLifetime time.Duration
	// IdleTimeout is extended whenever a loaded session is used. Zero disables
	// the limit; negative values are rejected.
	IdleTimeout time.Duration
	// ErrorHandler handles failures that prevent the manager from safely loading
	// or persisting session state. Eager load failures prevent the application
	// handler from running. Lazy load failures are discovered by the first session
	// accessor; the session and response are then aborted. When nil, the manager
	// logs the error and responds 500.
	ErrorHandler func(http.ResponseWriter, *http.Request, error)
	// Onload is called when a session is retrieved from storage. The returned
	// data replaces the in-memory value for this request and is not persisted
	// unless the handler also Set. Returning false deletes the session as if
	// Delete were called.
	Onload func(T) (T, bool)
	// Cookie settings.
	CookieOpts *SessionCookieOpts
	// Codec selects the persisted session encoding. Nil uses JSONCodec.
	Codec Codec
	// SessionIDAuthenticator authenticates the opaque session ID cookie. When
	// set, invalid cookies are rejected before they can cause a KV lookup.
	SessionIDAuthenticator Authenticator

	// EagerLoad loads session data from the KV store on every request. The default
	// is lazy loading: peek the session cookie up front and defer the KV Get until
	// the handler (or a DBSC endpoint) actually needs session state.
	EagerLoad bool

	// DBSCRefreshInterval defines how often the device must prove possession
	// of its private key. If 0, DBSC is disabled. Typical values are 5-15 minutes.
	DBSCRefreshInterval time.Duration
	// DBSCRegistrationPath is the URL path where the browser POSTs the registration
	// proof (Secure-Session-Response). When non-empty and DBSCRefreshInterval is set,
	// the manager handles POSTs to this path and responds with JSON session
	// instructions; your mux does not need a separate registration handler.
	// Secure-Session-Registration is attached automatically when a first-party
	// response saves the session (Set, Reset, flashes). Cross-site requests do
	// not offer. See Session.InitiateDBSCRegistration.
	DBSCRegistrationPath string
	// DBSCRefreshPath is the refresh_url placed in session instructions. Defaults
	// to "/dbsc/refresh" if empty. POSTs to this path are handled by the manager.
	DBSCRefreshPath string
	// DBSCOrigin is the origin sent in DBSC session instructions (scope.origin),
	// e.g. "https://example.com". Required when DBSCRefreshInterval is set.
	DBSCOrigin string
}

KVManagerOpts configures options specifically for the KV-based session manager

type Manager

type Manager[T any] struct {
	// contains filtered or unexported fields
}

Manager handles both session data and storage.

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"lds.li/session"
)

type exampleSessionData struct {
	UserID string `json:"user_id"`
}

func main() {
	sessions, err := session.NewKVManager[exampleSessionData](session.NewMemoryKV(), nil)
	if err != nil {
		panic(err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
		sess := sessions.FromContext(r.Context())
		sess.Reset()
		sess.Set(exampleSessionData{UserID: "alice"})
	})
	mux.HandleFunc("GET /me", func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprint(w, sessions.FromContext(r.Context()).Get().UserID)
	})
	handler := sessions.Wrap(mux)

	loginResponse := httptest.NewRecorder()
	handler.ServeHTTP(loginResponse, httptest.NewRequest(http.MethodPost, "/login", nil))

	profileRequest := httptest.NewRequest(http.MethodGet, "/me", nil)
	for _, cookie := range loginResponse.Result().Cookies() {
		profileRequest.AddCookie(cookie)
	}
	profileResponse := httptest.NewRecorder()
	handler.ServeHTTP(profileResponse, profileRequest)

	fmt.Println(profileResponse.Body.String())
}
Output:
alice

func NewCookieManager

func NewCookieManager[T any](aead cipher.AEAD, opts *CookieManagerOpts[T]) (*Manager[T], error)

NewCookieManager creates a new Manager that stores session data in cookies. It accepts any concurrency-safe cipher.AEAD. The manager generates and prefixes nonces for conventional AEADs; an AEAD with a zero nonce size, such as cipher.NewGCMWithRandomNonce, manages its own nonce framing.

Cookie-backed sessions cannot be revoked server-side. Delete removes the current browser's cookie and Reset reissues it, but previously copied cookie values remain valid until their configured expiration. Use NewKVManager when server-side revocation or session ID rotation is required.

func NewKVManager

func NewKVManager[T any](kv KV, opts *KVManagerOpts[T]) (*Manager[T], error)

NewKVManager creates a new Manager that stores session data in a KV store

func (*Manager[T]) FromContext

func (m *Manager[T]) FromContext(ctx context.Context) *Session[T]

FromContext returns the Session this Manager stored in ctx. It panics if this Manager did not install a session in ctx.

func (*Manager[T]) Wrap

func (m *Manager[T]) Wrap(next http.Handler) http.Handler

Wrap creates middleware that handles session management for each request. Session mutations must occur before the response is written or flushed; mutating a session after the response is committed panics.

type Session

type Session[T any] struct {
	// contains filtered or unexported fields
}

Session represents a tracked web session. A Session is scoped to one HTTP request and must not be accessed concurrently by multiple goroutines.

func (*Session[T]) AddFlash

func (s *Session[T]) AddFlash(flash Flash)

AddFlash appends a flash message and marks the session for saving.

func (*Session[T]) Delete

func (s *Session[T]) Delete()

Delete marks the session for deletion at the end of the request.

With a KV-backed manager, Delete removes the server-side session. With a cookie-backed manager, it only instructs the current client to remove its cookie; previously copied cookie values remain valid until expiration.

func (*Session[T]) Get

func (s *Session[T]) Get() T

Get returns the application data stored in the session.

The returned value is a copy of T. If T contains reference types such as maps, slices, or pointers, callers must call Set after making changes so the session is marked for saving.

func (*Session[T]) InitiateDBSCRegistration

func (s *Session[T]) InitiateDBSCRegistration(w http.ResponseWriter, r *http.Request)

InitiateDBSCRegistration adds Secure-Session-Registration immediately. When DBSC is enabled, the manager normally attaches this header automatically on a first-party response that persists session data (Set, Reset, flashes), as long as the session is not yet device-bound. Cross-site requests are skipped. Call this to offer without saving application data or to replace a pending challenge. It is a no-op when already bound or when this request would not auto-offer.

Requires DBSCRefreshInterval and DBSCRegistrationPath on the session Manager.

func (*Session[T]) IsDeviceBound

func (s *Session[T]) IsDeviceBound() bool

IsDeviceBound returns true if the session is cryptographically bound to a device.

func (*Session[T]) IsNew

func (s *Session[T]) IsNew() bool

IsNew reports whether persisted session data has not been loaded from storage. It is true for brand-new sessions and after Reset or Delete. On lazy-loaded managers, it stays true until a session accessor (Get, Set, etc.) triggers the store read.

func (*Session[T]) Reset

func (s *Session[T]) Reset()

Reset renews the session while retaining its data.

Call Reset before storing an authenticated identity so a previously issued session ID cannot be reused after login. Reset also restarts CreatedAt and UpdatedAt so IdleTimeout and MaxLifetime begin at authentication. With a KV-backed manager, Reset deletes the current server-side session and assigns a new session ID, invalidating the previous ID. With a cookie-backed manager, it only issues a newly encrypted cookie; previously copied cookie values remain valid until expiration. Applications requiring revocation or session ID rotation must use a KV-backed manager.

func (*Session[T]) Set

func (s *Session[T]) Set(data T)

Set replaces the application data and marks the session for saving.

func (*Session[T]) TakeFlashes

func (s *Session[T]) TakeFlashes() []Flash

TakeFlashes returns and removes all queued flash messages. It returns nil and does not mutate the session when no flashes are queued.

type SessionCookieOpts

type SessionCookieOpts struct {
	// Name must be a valid HTTP cookie name. Names beginning with __Host- must
	// use Path "/" and a secure cookie; names beginning with __Secure- must use
	// a secure cookie.
	Name string
	// Path must be an absolute cookie path beginning with "/".
	Path string
	// Insecure permits sending the cookie over HTTP. It should only be used for
	// local development without TLS.
	Insecure bool
	// Persist adds Max-Age so the browser may retain the cookie across restarts.
	// Persist requires MaxLifetime; an idle timeout alone is not a remember-me
	// policy.
	Persist bool
}

SessionCookieOpts configures the cookie emitted by a session manager.

Directories

Path Synopsis
internal
testsession
Package testsession provides the private bridge between session and sessiontest.
Package testsession provides the private bridge between session and sessiontest.
Package sessiontest provides helpers for unit testing handlers that use package session.
Package sessiontest provides helpers for unit testing handlers that use package session.

Jump to

Keyboard shortcuts

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