Session Store
HTTP session storage backed by Grove KV with auto-generated cryptographic IDs, configurable TTL, and touch-to-refresh support.
The SessionStore extension provides HTTP session management on top of any Grove KV backend. Sessions are stored as serialized values with automatic TTL expiry, and each session receives a cryptographically random 64-character hex ID.
Installation
import "github.com/xraph/grove/kv/plugins"Creating a Session Store
sessions := plugins.NewSessionStore(store)By default, sessions use the key prefix "sess" and a TTL of 30 minutes. Both are configurable via options:
sessions := plugins.NewSessionStore(store,
extension.WithSessionPrefix("mysess"), // keys become "mysess:<id>"
extension.WithSessionTTL(2 * time.Hour), // sessions expire after 2 hours
)Options
| Option | Default | Description |
|---|---|---|
WithSessionPrefix(prefix) | "sess" | Key prefix for all session keys |
WithSessionTTL(ttl) | 30m | Time-to-live for each session |
API Reference
Create
Creates a new session with the given data and returns the session ID.
func (ss *SessionStore) Create(ctx context.Context, data any) (string, error)The returned ID is a 32-byte cryptographically random value encoded as 64 hex characters. The session is stored at <prefix>:<id> with the configured TTL.
type SessionData struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
}
id, err := sessions.Create(ctx, &SessionData{
UserID: "u_123",
Username: "alice",
Role: "admin",
})
// id = "a3f8c1e9b2d7..." (64 hex chars)Get
Retrieves session data by ID. The destination must be a pointer to the expected type.
func (ss *SessionStore) Get(ctx context.Context, id string, dest any) errorvar data SessionData
err := sessions.Get(ctx, id, &data)
if err != nil {
// kv.ErrNotFound if the session expired or does not exist
}Update
Replaces the session data and resets the TTL.
func (ss *SessionStore) Update(ctx context.Context, id string, data any) errordata.Role = "superadmin"
err := sessions.Update(ctx, id, &data)Delete
Removes a session immediately.
func (ss *SessionStore) Delete(ctx context.Context, id string) errorerr := sessions.Delete(ctx, id)Touch
Refreshes the session TTL without changing the stored data. Useful for keeping active sessions alive.
func (ss *SessionStore) Touch(ctx context.Context, id string) errorerr := sessions.Touch(ctx, id)Exists
Checks whether a session exists without retrieving its data.
func (ss *SessionStore) Exists(ctx context.Context, id string) (bool, error)exists, err := sessions.Exists(ctx, id)Example: HTTP Middleware
A common pattern is to wrap the session store in HTTP middleware that loads the session on every request and provides it through the request context.
package middleware
import (
"context"
"net/http"
"github.com/xraph/grove/kv/plugins"
)
type contextKey string
const sessionKey contextKey = "session"
type SessionData struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Role string `json:"role"`
}
func SessionMiddleware(sessions *plugins.SessionStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session_id")
if err != nil {
next.ServeHTTP(w, r)
return
}
var data SessionData
if err := sessions.Get(r.Context(), cookie.Value, &data); err != nil {
next.ServeHTTP(w, r)
return
}
// Refresh the session TTL on every request.
_ = sessions.Touch(r.Context(), cookie.Value)
ctx := context.WithValue(r.Context(), sessionKey, &data)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// Login handler that creates a new session.
func LoginHandler(sessions *plugins.SessionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// ... authenticate user ...
id, err := sessions.Create(r.Context(), &SessionData{
UserID: "u_123",
Username: "alice",
Role: "admin",
})
if err != nil {
http.Error(w, "session error", http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: id,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
w.WriteHeader(http.StatusOK)
}
}Key Layout
Sessions are stored with the following key pattern:
<prefix>:<session_id>For example, with the default prefix:
sess:a3f8c1e9b2d74f6a8e1c3d5b7a9f2e4d6c8a0b1e3f5d7a9c2b4e6f8a0d1c3eSecurity Notes
- Session IDs are generated using
crypto/rand(32 bytes, 256 bits of entropy), making them resistant to brute-force guessing. - Always transmit session IDs over HTTPS and set cookies with
HttpOnly,Secure, andSameSiteflags. - Use a short TTL and call
Touchon active requests rather than setting a long TTL.