75 lines
2.9 KiB
Go
75 lines
2.9 KiB
Go
// Package session builds and configures the application's server-side
|
|||
|
|
// session manager. We use github.com/alexedwards/scs (SCS) for the session
|
||
|
|
// framework itself, backed by Redis for storage.
|
||
|
|
//
|
||
|
|
// "Server-side session" means: the browser only ever holds a random,
|
||
|
|
// meaningless token in a cookie. All the actual session DATA (which user
|
||
|
|
// is logged in, etc.) lives in Redis, keyed by that token. This is in
|
||
|
|
// contrast to storing data directly inside a signed/encrypted cookie -
|
||
|
|
// server-side sessions can be instantly revoked (just delete the Redis
|
||
|
|
// key), don't grow the cookie size as you store more data, and never
|
||
|
|
// expose their contents to the browser at all.
|
||
|
|
package session
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/alexedwards/scs/redisstore"
|
||
|
|
"github.com/alexedwards/scs/v2"
|
||
|
|
"github.com/gomodule/redigo/redis"
|
||
|
|
|
||
|
|
"git.hamidsoltani.com/hamid/go-simple-api/internal/config"
|
||
|
|
)
|
||
|
|
|
||
|
|
// New builds a *scs.SessionManager wired up to Redis, with cookie settings
|
||
|
|
// appropriate for an authentication system.
|
||
|
|
func New(cfg config.Config) *scs.SessionManager {
|
||
|
|
// redigo's connection pool - conceptually identical to *sql.DB's pool
|
||
|
|
// from the database package, just for Redis instead of MySQL. Dial is
|
||
|
|
// a function the pool calls whenever it needs to open a new
|
||
|
|
// connection.
|
||
|
|
pool := &redis.Pool{
|
||
|
|
MaxIdle: 10,
|
||
|
|
Dial: func() (redis.Conn, error) {
|
||
|
|
return redis.Dial("tcp", cfg.RedisAddr)
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
manager := scs.New()
|
||
|
|
|
||
|
|
// By default scs stores session data IN MEMORY, which would be lost on
|
||
|
|
// every restart and wouldn't work at all if you ever ran more than one
|
||
|
|
// instance of this app behind a load balancer. Setting .Store swaps
|
||
|
|
// the storage backend to Redis while keeping the exact same
|
||
|
|
// Put/Get/Destroy API - the rest of the app never needs to know Redis
|
||
|
|
// is involved at all.
|
||
|
|
manager.Store = redisstore.New(pool)
|
||
|
|
|
||
|
|
// How long a session stays valid after creation.
|
||
|
|
manager.Lifetime = 24 * time.Hour
|
||
|
|
|
||
|
|
// --- Cookie security settings ---
|
||
|
|
manager.Cookie.Name = "session_id"
|
||
|
|
|
||
|
|
// HttpOnly: JavaScript in the browser cannot read this cookie via
|
||
|
|
// document.cookie. Blocks a large class of XSS-based session theft.
|
||
|
|
manager.Cookie.HttpOnly = true
|
||
|
|
|
||
|
|
// SameSite=Lax: the cookie IS sent on normal top-level navigation
|
||
|
|
// (e.g. clicking a link to this site, or Google redirecting back to
|
||
|
|
// our OAuth callback), but is NOT sent on cross-site requests
|
||
|
|
// triggered by another page (e.g. a malicious auto-submitting <form>
|
||
|
|
// pointed at our /logout). This is our primary CSRF defense for the
|
||
|
|
// cookie-based session.
|
||
|
|
manager.Cookie.SameSite = http.SameSiteLaxMode
|
||
|
|
|
||
|
|
// Secure: when true, the browser will refuse to send this cookie over
|
||
|
|
// plain HTTP, only HTTPS. We only turn this on in production, because
|
||
|
|
// enabling it during local development (over http://localhost) would
|
||
|
|
// silently break the cookie entirely.
|
||
|
|
manager.Cookie.Secure = cfg.Env == "production"
|
||
|
|
|
||
|
|
return manager
|
||
|
|
}
|