alexedwards / scs Goto Github PK
View Code? Open in Web Editor NEWHTTP Session Management for Go
License: MIT License
HTTP Session Management for Go
License: MIT License
I'm new to Go session management. How does this compare to Gorilla Session?
Thanks,
Andrew
when use method 'Manager.Use', i do not understand why using conditon 'm.opts.idleTimeout > 0 ',
i think it is more reasonable to remove it
can't be used with websocket
This is my code snippet, I use Redis storage, and define the IdleTimeout value is 20 minutes and the Lifetime value is 2 hours, but the session data is always in 20 minutes of failure, the page refreshes does not seem to automatically extend the idle timeout time?
SessionManager = scs.NewManager(
redisstore.New(&redis.Pool{
MaxIdle: 10,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", "127.0.0.1:6379", redis.DialDatabase(0))
},
IdleTimeout: time.Duration(20) * time.Minute,
}),
)
SessionManager.IdleTimeout(20 * time.Minute)
SessionManager.Lifetime(time.Duration(2 * time.Hour)
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)
As per this comment:
My middleware unit testing used the v1 Token()
function for verifications of client-server responses. Does v2 have a way for middleware to access the private token
directly?
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?
I use the scs/session
package in Echo, and if I export anything to the browser in the HTTPErrorHandler
function of Echo, the Echo of logger will print such warning messages:
Level:WARN, file:context.go, line:484, message:response, already, committed.
If I don't use session, there won't be such a problem.
package main
import (
"time"
"github.com/alexedwards/scs/engine/memstore"
"github.com/alexedwards/scs/session"
"github.com/labstack/echo"
)
func main() {
app := echo.New()
app.Debug = true
sessionManager := session.Manage(memstore.New(12 * time.Hour))
app.Use(echo.WrapMiddleware(sessionManager))
app.HTTPErrorHandler = errorHandler
app.Start(":80")
}
func errorHandler(err error, ctx echo.Context) {
ctx.String(200, "test")
}
Hi,
Is it possible to change the session's expiry on a single session basis? I need some of my users to have much longer sessions than others, but it seems the session.IdleTimeout
call has a global affect. Am I missing something?
Cheers,
Shmul
Well done @alexedwards on re-imagining scs. ๐ฅ This was a comprehensive rewrite. A number of items I appreciate:
Status
Status
IdleTimeout
and Lifetime
were split from the old options
.go.mod
added for go modules supportFor 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.
IdleTimeout field is time type
l want set session name cookie's expires "Session"
I tried to use scs with echo on very simple application, but I get this error. I use session.PutObject
to store the data, and session.GetObject
to read the data.
package main
import (
"github.com/alexedwards/scs"
"github.com/labstack/echo"
"net/http"
)
var sessionManager = scs.NewCookieManager("u46IpCV9y5Vlur8YvODJEhgOY8m9JVE4")
type UserModel struct {
ID string
Name string
Age int
}
func main() {
const SESSION_ID = "id"
e := echo.New()
e.HTTPErrorHandler = func(err error, c echo.Context) {
report, ok := err.(*echo.HTTPError)
if !ok {
report = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
c.Logger().Error(report)
c.JSON(report.Code, report)
}
e.Use(echo.WrapMiddleware(sessionManager.Use))
e.GET("/index", func(c echo.Context) error {
session := sessionManager.Load(c.Request())
user := new(UserModel)
user.ID = "001"
user.Name = "Noval"
user.Age = 12
err := session.PutObject(c.Response(), SESSION_ID, *user)
if err != nil {
return err
}
return c.Redirect(http.StatusTemporaryRedirect, "/home")
})
e.GET("/home", func(c echo.Context) error {
session := sessionManager.Load(c.Request())
user := new(UserModel)
err := session.GetObject(SESSION_ID, user)
if err != nil {
return err
}
return c.JSON(http.StatusOK, user)
})
e.Logger.Fatal(e.Start(":9000"))
}
The error occur on the part where the session is being read.
{"Code":500,"Message":"illegal base64 data at input byte 5","Internal":null}
Hi,
Firstly, thank you for this package, I've been rolling my own similar one and in frustration Google again and found this project. This greatly simplifies session management for me, without requiring bring in a library or using an overly simple package like gorilla/sessions (nothing wrong with it, I just needed more).
One piece that I'd also like this to handle is the ability to redirect a user if a session doesn't exist (new user or expired session). For example, keeping sessions for all handlers, except a few that are for logged in users only.
I'm currently handling this with my own middleware successfully. If the user does not have the key username set in the session, they are redirected to /login
.
func MustExist(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ok, _ := session.Exists(r, "username"); !ok {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
next.ServeHTTP(w, r)
})
}
I'd love to know if there's a way to detect if there's a session at all, as in my case, and the common (but not all) cases, a user would just want to know that someone is logged in.
This isn't a lot of code to write, but it's also possible to get wrong, and I'd want to copy this exact signature across a few sites, so having this provided by the session
package would be ideal.
To do this, I think it'd be best if it's just a check if the user has a valid session, but the problem I found was that an earlier handler detects the lack of session and added one. So this handler was never able to detect the lack of session. Is there a way to detect if a session doesn't currently exist for this user without adding storing keys?
Would it be possible to provide this middleware as part of the session
package? I had a stab at an API:
type MustExist struct {
Key string
RedirectURL string
}
func (m *MustExist) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ok, _ := session.Exists(r, m.Key); !ok {
http.Redirect(w, r, m.RedirectURL, http.StatusFound)
return
}
next.ServeHTTP(w, r)
})
}
And it could be used like:
&MustExist{Key: "username", RedirectURL: "/login"}.Handler
But this is still requiring a string key be set, and the API itself is quite ugly. As a user, I'd prefer to just use:
// MustHaveSession redirects a user to redirectURL if no session exists (or it's expired).
// No other handlers or middleware are executed.
func MustHaveSession(redirectURL string) func(http.Handler) http.Handler
Is this possible?
How do I apply it to my application?
I am a novice
Session usually represents a single session.
Is Sessions or SessionManager better?
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/alexedwards/scs"
)
var manager = scs.NewCookieManager("u46IpCV9y5Vlur8YvODJEhgOY8m9JVE4")
type Site struct {
}
func (site Site) ServeHTTP(w http.ResponseWriter, req *http.Request) {
session := manager.Load(req)
session.PutString(w, "key", "value")
fmt.Println(session.GetString("key"))
session2 := manager.Load(req)
// should be able to get saved value
fmt.Println(session2.GetString("key"))
}
func main() {
Server := httptest.NewServer(Site{})
http.Get(Server.URL)
}
https://github.com/alexedwards/scs/blob/master/session.go#L41
Maybe should save session into request's context, and load it from context next time?
With MySQL before version 5.6, I get SQL errors in mysqlstore.go. They are caused by UTC_TIMESTAMP(6)
. The parameter was introduced in MySQL 5.6.4 (see the docs) and "is given to specify a fractional seconds precision from 0 to 6". In MySQL 5.6, they also changed the default format from 'YYYY-MM-DD HH:MM:SS' or YYYYMMDDHHMMSS.uuuuuu
to 'YYYY-MM-DD HH:MM:SS' or YYYYMMDDHHMMSS
.
I'm not sure if it a "MySQL version switch" in mysqlstore would be feasible. But is it necessary to store the expiry timestamp with such a precision at all? Would't one second be precise enough? Then we could omit the parameter of UTC_TIMESTAMP(6)
.
Looking for some feedback on whether or not this is a good idea. I'd like finer control of Touch(). Why? For a high-volume site, each Touch is a new update to deadline and therefore a new write to the data store. While some handlers will always uses sessions others will not, such as serving static files. Admittedly there are other ways to design this. For example in middleware, I could skip calling Touch for any path serving static files. But a few seconds delay in Touch would serve the same purpose. (And for those static files, I'm still updating global statistics - just not session stats on the static files - which I'm not tracking.)
What if there was a TouchIntervalRate to control how often the deadline gets updated via touch? It could manifest itself a few ways. Arguably the easiest is a new function, session.TouchWithInterval()
.
// in session.go
// TouchWithInterval func controls the rate at which touches are automatically written via Touch
// there can be quite a few quick touches - which mean writes back to the datastore
// would require a private "isNew" var added to manager.Session
func TouchWithInterval(w http.ResponseWriter, interval time.Duration) error {
if s.loadErr != nil {
return s.loadErr
}
doTouch := true
if !s.isNew && interval > 0 {
timeToTouch := time.Now().Subtract(s.deadline).Add(interval)
doTouch = timeToTouch.Afer(time.Now())
}
if doTouch {
return Touch(w)
}
return nil
}
Thoughts?
Due to the nature of the middleware returned from session.Manage(), I'm not seeing a way to chain this middleware with Negroni.
Is there a way to use the middleware provided by scs within negroni?
How are folks handling stale sessions in the data store? For example, redis has its expire command but by default it is set to never expire.
If a vacuuming routine compares time.Now()
> deadline
, then the session should be deleted. That's the logic, yes?
Is there any use case for including a saved
property (the date when a session was last saved) - in addition to the deadline? Right now I can't think of any. Maybe I just answered my own question but am double-checking with others.
Why are we wasting 11bytes (15 later when base 64 encoded) of cookie data with converting UnixNano to string representation.
The session middleware populates the context with the session using a custom string type as a key. Thus it cannot be accessed from the outside without the *http.Request
object through Manager.Load(req)
.
Would you accept a patch adding Manager.LoadFromContext(ctx)
method so one can access the session in a scope where the request is not available but its context is?
I use SCS as the session middleware in the Echo framework, but in the process of operating session, if the Redis service is closed, it triggers the log.Println() in the Use() function.
My Code is like this:
echo.Use(echo.WrapMiddleware(session.Use))
I hope this error triggers Echo's error and shouldn't print error messages directly on the server console.
How do I do that?
If I invoke a function like GetObject
, it sometimes has a non-nil error that comes from the underlying storage system. In order to handle these gracefully, I'd need to program against the underlying storage system to handle something like "cache key not found". I'm specifically talking about the memcached
backend here, but it looks like other storage systems use the same leaky abstraction. This defeats the abstraction offered by SCS since I can't easily handle errors generated by multiple backends in the same way. In short, my code that uses scs.Manager
shouldn't need to know about memcache.ErrCacheMiss
.
To fix this problem, I propose implementing new SCS error types that can map onto the most common storage backend errors in a sensible way:
ErrNotFound
ErrBackendFailure
ErrSomethingElse...
This would provide abstraction over the underlying errors without hiding too much about what's really happening. I'd be happy to program this myself and rework all of the storage backends to use it.
What do you think?
For an API I needed to store the session-id somewhere else instead of the cookie header field. So I added the possibility to also use a string instead of an http.Request. Sadly it breaks the public API so I won't issue a pull request, but maybe someone else has a better idea making it work without breaking changes.
See discussion in #23
This could be addressed by implementing Middleware which uses a custom ResponseWriter to intercept HTTP Responses, save the session data to the store and set the session cookie - similar to v0.1 of SCS (https://github.com/alexedwards/scs/blob/v0.1.0/session/manager.go)
Downside is that this causes problems with frameworks/applications which also try to call WriteHeader. See #15
A workaround might be to set the session cookie header each time the session is modified (or even loaded) and only handle saving to the store in the middleware. I think that would work for most stores, except the CookieStore, which needs the response headers to not have already been written.
how can I get notified when a session expires by its lifetime?
I need it because I want to set the user state as offline. when he or she logs out or it's session lifetime expires.
thanks.
I am new to golang, using go1.10 and trying to compile
I get undefined: http.SameSite error during compiliation
Stackoverflow seems to suggest that it is because of go version, although this is unconfirmed
https://stackoverflow.com/questions/55895541/encountered-an-error-samesite-http-samesitelaxmode
Sorry, i cant share the code right now. Just need some light for finding the issue. Sometimes the session just doesnt work and create many tokens for the same user (im using mysqlstore, so is easy to see this). Now session only works after calling RenewToken. Did this descriptions identify with any common misuse of the library? Ive already tried a lot of things. Thanks and sorry for the lack of more info.
I use echo v3, I repeat the request controller (refresh page), it will create a number of records in redis scs / session, this is a bug or I use the wrong method?
the code is probably this:
main.go
engine := redisstore.New(
&redis.Pool{
MaxIdle: 10,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", "127.0.0.1:6379")
},
},
)
sessionManager := session.Manage(
engine,
session.HttpOnly(true),
session.IdleTimeout(20*time.Minute),
session.Lifetime(3*time.Hour),
)
App.Use(echo.WrapMiddleware(sessionManager))
Controller
func Index(ctx echo.Context) error {
data := make(map[string]interface{})
session.PutString(ctx.Request(), "sess", "ok")
data["sess"], _ = session.GetString(ctx.Request(), "sess")
return ctx.Render(200, "index.jet", data)
}
Refresh three pages, redis appeared in three records, I think this is not normal?
Is it possible to support multiple sessions per request? It seems that now ContextName
and CookieName
are global variables.
Any objection to using securecookie to write and load the session token/key/id from the cookie?
I think it's a good idea to use signed cookies to prevent tampering. e.g. you can avoid unnecessary queries to the session store by throwing away invalid cookies
With you blessing, I'll open a PR to integrate securecookie
into scs
with the following features:
scs
cookies. We could accomplish this with one of the following approaches:
secureName
field to options
structCongratulations on v2.
Would you consider adding a function similar to Token() in v1 for fetching the token value? I have a problem to keep track on the latest login by session token because I cannot get the value after calling RenewToken. Maybe is there an alternative way?
it'd work for me in my case If adding this function in data.go, but only a thought.
func (s *Session) Token(ctx context.Context) string {
sd := s.getSessionDataFromContext(ctx)
return sd.token
}
Thanks for the library.
I use SCS to implement session functionality in the echo framework, but I delete the sessionid in cookie and then refresh the browser and output the error:
redigo: unexpected type for Bytes, got type []interface {}
redigo: unexpected response line (possible server error or unsupported concurrent read by application)
write tcp 127.0.0.1:49741->127.0.0.1:6379: use of closed network connection
My code:
//Connect Redis
func connectRedis() (redis.Conn, error) {
conn, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
return nil, err
}
return conn, err
}
//Configure SCS
func SetSession() error {
redisConn, err := connectRedis()
if err != nil {
return err
}
redisPool := redisstore.New(redis.NewPool(func() (redis.Conn, error) {
return redisConn, nil
}, 1))
SessionManager = scs.NewManager(redisPool)
SessionManager.Name("sessionid")
SessionManager.Path("/")
SessionManager.Lifetime(60 * time.Minute)
SessionManager.Secure(false)
SessionManager.HttpOnly(true)
SessionManager.IdleTimeout(20 * time.Minute)
SessionManager.Persist(true)
return nil
}
//Echo middleware
func SessionMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
//update session idletime
session := SessionManager.Load(ctx.Request())
err := session.Touch(ctx.Response().Writer)
if err != nil {
return err
}
return next(ctx)
}
}
}
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"))
}
Can you please tell if SessionManager is global in scope meaning bootstrapped from some Init function
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.
Ben Johnson, the creator of bolt, has declared his project as feature complete and won't provide further support due to lack of time (see boltdb/bolt@fa5367d).
CoreOS forked the project over to bbolt and pushes development of the project forward. Popular projects based on boltdb, like storm are already on the migration path to bbolt.
Having said that, bolt works great for me, and you've already moved your API to v1, so switching the bolt backend over to bbolt would probably break your v1 contract.
Just opening this issue to raise awareness to the change in bolt's future.
And thanks for this very fine project ๐
Since Opts is not exposed, custom middleware extension is not possible, in case I want to write my own middleware.
Referring
Line 114 in 876a0fd
What if by adding manager.Opts() method, which returns opts.
Hi,
first thank you for this great package.
after some hours of trying to get it work, i decided to give a try the sample code
i use curl put get but hello world is not printed.
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
when i want to get session from context, i need type "SessionName"
Abstract: Example code not working - resulting in stacktrace
At first, I suspected some issues with the filesystem (though /tmp
should be writable).
But even if you substitute the bolt
examplecode by the memstore
example code, the app crashes.
Operating system:
Linux MYBOX 5.1.16-1-default #1 SMP Wed Jul 3 12:37:47 UTC 2019 (2af8a22) x86_64 x86_64 x86_64 GNU/Linux
Go Version: go version go1.12.6 linux/amd64
As mentioned in the project's README, the repo at https://github.com/garyburd/redigo has now moved to https://github.com/gomodule/redigo
Is it possible to set a list of folders that the session manager needs to ignore? This is quite useful for static files.
Hello - would it be possible to release a new version? I noticed that there is an unreleased change with the updated location of the redigo library.
Appreciate your efforts!
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? ๐
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.