Sessions: What Would gorilla/sessions v2 Look Like?

Created on 27 Jan 2017  ·  25Comments  ·  Source: gorilla/sessions

With Go 1.7's request.Context() requiring a (non-trivial!) breaking API change, I figured it would be a good time to discuss what a "v2" of this package would look like. Further, with golang/dep on the horizon, vendoring/pinning dependencies is more common-place, allowing us to 'safely' leave v1 API users as-is whilst improving the library for others.

What I see as key changes:

  • [ ] Supporting request.Context (only)
  • [ ] Enriching the Store interface: New, Get, Save, Delete rather than relying on MaxAge to trigger it
  • [ ] Better built-in stores: BoltDB instead of a FilesystemStore (still uses the filesystem, but is a more scalable store)
  • [ ] Improved crypto interface: enforce algorithms, key size, and don't give users a choice. De-emphasize encrypting (an option, rather than a first-class param in New)
  • [ ] Fix the order of - Save(w, r) rather than (r, w)
  • [ ] Improve the user-experience around Save (forgetting to save sucks, and is common enough!)
  • [ ] Make sessions.Values better (setters, getters, rather than a map)
  • [ ] Simplify the internal registry: call it a cache (what it really is), and perhaps have it auto-save at the end of a request?
  • [ ] Out of the box middleware that makes a session available? Checks for one?
  • [ ] JSON as the default encoder, not gob. Faster, less overhead (in bytes); retain gob for those who really need to store blobs.
  • [ ] Potentially supporting more than just cookies for non-cookie stores (Bearer tokens, but not JWTs).

There is no schedule for this yet. Open to feedback.

breaking change bug help wanted proposal question stale

Most helpful comment

I think especially supporting the golang context package would be a big step forward. It's even backwards compatible, since all interfaces already accept a request to get the value.

Any timeline on this?

All 25 comments

I agree with pretty much all of this.

Great. I'd also likely simplify gorilla/securecookie as part of this: the internals have grown over time, and moving from the current AES-CTR + MAC approach to either HMAC-SHA-512/256 or ChaCha20+Poly1305 (https://godoc.org/golang.org/x/crypto/chacha20poly1305) and thus simplifying the crypto.

securecookie is otherwise a small lib; I think it was a mistake growing the error interface the way we did, but alas.

+1 for pretty much all of these.

You've probably already considered this, but one thing I learnt from building SCS was that although using JSON for the default encoding is sensible from a performance view it has the potential to make things more complicated for users (assuming a map[interface{}]interface{} is still used behind the scenes).

For example, retrieving a previously stored time.Time object. With gob encoding, the user can just retrieve the interface{} value and assert it to a time.Time. It's fairly simple and straightforward. With underlying JSON encoding, the user needs to know to type assert the interface{} value to a string and then call time.Parse(time.RFC3339, ...) to get it back to a time.Time object.

Similarly, dealing with integers and floats and json.Number is a bit of a pain.

You could implement helpers (like I did in SCS), or there's probably something clever that can be done with a custom JSON unmarshaller. Either way, it would be good to keep usage no more difficult than it is with gob encoding.

A couple of further suggestions.

It would be nice if both absolute and idle timeouts could be supported (in line with the OWASP recommendations).

For the non-cookie stores, there would ideally be an clear and baked-in way to renew session IDs after login/logouts etc to help prevent session fixation attacks (possibly as part of the interface along with New, Save etc). At the moment this isn't possible (at least as far as I can see) without a clunky workaround of creating a new session with a different name and copying the data over.

Thanks for the input @alexedwards -

  • gob was (is!) the most compatible, so I may yet keep it, and just make changing between them a little easier.

It would be nice if both absolute and idle timeouts could be supported (in line with the OWASP recommendations).

Yes, agreed. Some opts.MaxAge and opts.IdleTimeout would be useful. I'd need to think about how to employ that without also performing a Set-Cookie on every response too.

For the non-cookie stores, there would ideally be an clear and baked-in way to renew session IDs after login/logouts etc to help prevent session fixation attacks (possibly as part of the interface along with New, Save etc).

Agree - a Refresh method would be useful here, or an argument to Save that achieves the same effect (issues a new ID).

https://github.com/gorilla/securecookie/issues/43 tracks a "v2" of securecookie that will underpin some of this work

This looks great. One question: have you considered/what are the tradeoffs of putting included stores with non stdlib imports (cookie, boltdb) into separate packages?

Thank you for all your work!

@ccahoon The stores would each be in separate packages ("sub-packages") so that if you don't use BoltDB, it's not imported.

Redis, MySQL & Postgres backends already exist, and the Redis store is
rather polished. All of the third party stores are linked in the README.
On Thu, Mar 23, 2017 at 4:47 PM Kyle Terry notifications@github.com wrote:

Redis and database (sql) support would be cool too. If you are running n
instances of the application using gorilla/sessions you need to be able to
centralize your sessions for round robin.


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/gorilla/sessions/issues/105#issuecomment-288893992,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABIcDfaPALfkjOw5FzdDoM_MHs9RbCYks5rowSIgaJpZM4LwM5f
.

I think especially supporting the golang context package would be a big step forward. It's even backwards compatible, since all interfaces already accept a request to get the value.

Any timeline on this?

@Niondir No timeline. I want to wait until golang/dep hits 1.0 so I existing users have a clear way to pin to pre-2.0. As it is, those pulling from master will see a ton of breakage immediately, which is going to be painful as gorilla/sessions sees use from a wide range of developers (newbie Gophers to experienced).

A way to disable the in memory cache would be great. This package is unusable in applications that run multiple instances.

Can you share more details here? The cache should only live for one
request; sessions are otherwise not stored in memory across requests.
On Mon, Dec 18, 2017 at 5:16 AM Romain Menke notifications@github.com
wrote:

A way to disable the in memory cache would be great. This package is
unusable in applications that run multiple instances.


You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
https://github.com/gorilla/sessions/issues/105#issuecomment-352422398,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABIcETDijyILlKONBJDuBlr6REcQgK6ks5tBmWTgaJpZM4LwM5f
.

@elithrar My bad, have been looking at too many packages that utilise an in memory cache that is shared across requests. Looking through the source code it was not immediately clear that the Registry was isolated to a single request. Thx for pointing this out!

Is dep 1.0 still a blocker? According to the status in the readme, dep is safe for production usage: https://github.com/golang/dep#current-status
Namely, the manifest and lock files are stable. See: https://github.com/golang/dep/wiki/Roadmap#timeline

My understanding is that if gorilla/sessions starts using dep, downstream packages shouldn't be affected. See: https://github.com/golang/dep/blob/master/docs/FAQ.md#my-dependers-dont-use-dep-yet-what-should-i-do

Any downstream package that's using the master branch should be aware that they've signed up for backwards incompatible changes. The other option would be to create a new repo/package if preserving backwards compatibility is paramount.

Any downstream package that's using the master branch should be aware
that they've signed up

That hasn’t been the status quo, and the lack of an option in the toolchain
is the pain point for regular users.

dep being in the Go toolchain is strongly preferred before we use it to
break the API here.

We may do it earlier than that, but not without careful consideration.

The other option would be to create a new repo/package if preserving
backwards compatibility is paramount.

That ends up islanding old users & causing a lot of confusion. I’ve not
seen that work well to date.
On Wed, Jan 10, 2018 at 11:19 PM Dale Hui notifications@github.com wrote:

Is dep 1.0 still a blocker? According to the status in the readme, dep is
safe for production usage: https://github.com/golang/dep#current-status
Namely, the manifest and lock files are stable. See:
https://github.com/golang/dep/wiki/Roadmap#timeline

My understanding is that if gorilla/sessions starts using dep, downstream
packages shouldn't be affected. See:
https://github.com/golang/dep/blob/master/docs/FAQ.md#my-dependers-dont-use-dep-yet-what-should-i-do

Any downstream package that's using the master branch should be aware that
they've signed up for backwards incompatible changes. The other option
would be to create a new repo/package if preserving backwards compatibility
is paramount.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/gorilla/sessions/issues/105#issuecomment-356847518,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABIcO-Q_kwTZCommIiIm02ce7dd2afRks5tJbYWgaJpZM4LwM5f
.

dep ensure -add github.com/gorilla/sessions@^1.0.0 should properly pin the package.
But yeah, anyone who's used (or using) go get will have a problem and I guess it's too early to force all of the downstream packages to start using dep.

What do you think about creating a v2 branch to start all of the v2 work, tagging new releases off of the v2 branch, and requiring v2 users to use dep? The v2 branch could be merged back into master when dep is part of the toolchain.

Yes, that's an option I've considered.

I'd also need to start the work on securecookie first, as sessions relies
on securecookie, and I'd like to update that library.

On Thu, Jan 11, 2018 at 11:45 AM Dale Hui notifications@github.com wrote:

dep ensure -add github.com/gorilla/sessions@^1.0.0 should properly pin
the package.
But yeah, anyone who's used (or using) go get will have a problem and I
guess it's too early to force all of the downstream packages to start using
dep.

What do you think about creating a v2 branch to start all of the v2 work,
tagging new releases off of the v2 branch, and requiring v2 users to use
dep? The v2 branch could be merged back into master when dep is part of
the toolchain.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/gorilla/sessions/issues/105#issuecomment-357039763,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABIcFVJM02Np6zhOehopNPu2dmzUwPkks5tJmTEgaJpZM4LwM5f
.

Cool, lemme know where I can help out by tagging me on any issue in sessions or securecookie

If you have any opinions on:

  • The securecookie v2 punch list:
    https://github.com/gorilla/securecookie/issues/43
  • What an updated sessions API would look like (read: what problems would
    we solve by changing the current API? Do we even need to?)
  • Whether the store API needs changes.

There's been a handful of issues tagged as "v2" that provide some
background / user feedback, but I want to make sure we solve either new
problems, or solve old problems better, by breaking the API. Breaking for
the sake of breaking makes less sense ;)

On Thu, Jan 11, 2018 at 4:20 PM Dale Hui notifications@github.com wrote:

Cool, lemme know where I can help out by tagging me on any issue in
sessions or securecookie


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/gorilla/sessions/issues/105#issuecomment-357104851,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABIcOppAU5Mj8YyU6US-lRBQ14wgb8oks5tJqVJgaJpZM4LwM5f
.

I think most of the improvements listed in the issue description are good to have.
The changes I find the most useful (in-order) are:

  1. Enriching the Store interface

    • The store API needs to be revamped and re-designed. An implementer of a session store shouldn't be responsible for setting the HTTP cookie in the response. Cookie management and the session lifecycle should be handled by the sessions package, not by the store.

    • Session stores should only have one concern which is the persistence of the session data. Pushing core functionality (even parts of it) to the stores will result in inconsistent behaviors between 3rd party stores and make it harder to change core functionality in the future.

    • Simplifying the Store receiver methods to not use http.Request and http.ResponseWriter would be a breaking change.

  2. Supporting request.Context (only)

    • Would the registry/cache still be necessary if request.Context was used?

    • A cleaner way to cache sessions would be to implement the cache as a session store which "wraps" an existing session store. Then the developer can choose between different caching implementations or use multiple caching layers. e.g. an in-process-memory cache, memcached, and SQL store

  3. Out of the box middleware that makes a session available

    • I'd also have the middleware save the session which would address this improvement: "Improve the user-experience around Save (forgetting to save sucks, and is common enough!)"

    • And help with the "Enriching the Store interface" improvement

  4. Improved crypto interface
  5. JSON as the default encoder

Regarding "Make sessions.Values better (setters, getters, rather than a map)", I'd like to keep the sessions.Values map but also expose type-casting getters and setters. The developer may have their own way of handling type-casting failures.

Thanks for the feedback @dhui

The store API needs to be revamped and re-designed. An implementer of a session store shouldn't be responsible for setting the HTTP cookie in the response. Cookie management and the session lifecycle should be handled by the sessions package, not by the store.
Session stores should only have one concern which is the persistence of the session data. Pushing core functionality (even parts of it) to the stores will result in inconsistent behaviors between 3rd party stores and make it harder to change core functionality in the future.

Agree. Stores need the session ID (to allow lookup), TTL (to allow expiry) and the session payload itself. There are a few approaches here - I'll omit context.Context from the signatures for now as that warrants further discussion.

type Store interface {
    Get(id string) (*Session, error)
    Save(session *Session) error
    Expire(id string) (bool, error)
}

Stores would need to care about marshalling *Session to the correct format: we could alternatively send id string, exp time.Time, data []byte where data is already marshalled, but I feel marshalling is within the purvey of a Store.

Supporting request.Context (only)
Would the registry/cache still be necessary if request.Context was used?

No, but that goes back to the larger, breaking change. We would only support context.Context in v2 as gorilla/context does not cooperate when used alongside request.WithContext due to the way it shallow clones a request.

Out of the box middleware that makes a session available

How would it make it available? In the request context? If so, fetching it from there is the same amount of code—except, with type assertions as context.Value is an interface{}—as calling sessions.Get(ctx, name).

Saving, on the other hand, can be done (mostly) automatically, although if a user writes to the response body before the middleware runs, we'll run into issues. The middleware would need to hijack the http.ResponseWriter and defer writes so that it can Save. There's also the fact that you may not wish to Save on every request.

Improved crypto interface
I'm a fan of having safe defaults but allowing the developer to customize security for their usecase. A good way of doing this is by exposing a separate "unsafe" or "hazmat" package. Inspired by https://golang.org/pkg/unsafe/ and https://cryptography.io/en/latest/

I'd need to see an extremely convincing argument for this. I don't see any sensible use-case for why a developer needs to customize the cryptographic primitives of a sessions library. You should not need to replace the CSPRNG, and you shouldn't need to change the AEAD. We'll use HMAC-SHA-512 for auth-only mode & XSalsa20-Poly1305 for encrypted mode (AEAD).

JSON as the default encoder

Yes!

Regarding "Make sessions.Values better (setters, getters, rather than a map)", I'd like to keep the sessions.Values map but also expose type-casting getters and setters. The developer may have their own way of handling type-casting failures.

Agree!

You're welcome! Thanks for being open and receptive to different ideas!

Stores would need to care about marshalling *Session to the correct format: we could alternatively send id string, exp time.Time, data []byte where data is already marshalled, but I feel marshalling is within the purvey of a Store.

Agreed. I think the Store should be responsible for storing the data since there may be more efficient storage mechanisms for the store besides []byte. The Session will need all relevant fields exported for stores to support marshalling and unmarshalling.

How would it make it available? In the request context?

Yeah, I'm a fan of the middleware setting the Session using an unexported context key type in request.Context(). That way, there's a bit less boiler plate code for the consuming developer.
Something like sessions.SessionFromRequest(http.Request) (*Session, error). We could also drop the error and create a new Session if it's used w/o the middleware, but that could lead to subtle bugs if there are multiple calls using the same request.

The middleware would need to hijack the http.ResponseWriter and defer writes so that it can Save.

I'm not familiar w/ the Hijacker interface, but it seems like we'd want compatibility with HTTP/2 connections. I'm not sure if wrapping the ResponseWriter to set the cookie after WriteHeader() called is the best route.

There's also the fact that you may not wish to Save on every request.

Good point regarding the middleware. We could have 2 middlewares:

  1. one that saves and sets the cookie
  2. one only verifies the session).

I'd need to see an extremely convincing argument for this.

Haha, I don't think I have one but I'll try... My best argument is that there's no one size fits all for security. Security is about making trade-offs between usability, performance, and protection against expected attack vectors. The best person to make that decision is the the app developer. Providing a clean crypto interface (sane defaults and allowing customization with ample warnings) would allow developers to customize security for their usecase. e.g. They could use HSMs and/or TRNGs if they wanted to although it's probably overkill for sessions...

One other thing regarding the crypto interface: make sure there's a way to change/upgrade the algorithm, key size, and work factor. These things are always change, so a system that can auto upgrade the algo, key size, and work factor will save a lot of future headaches.

This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days.

This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days.

Should this be reopened?

Was this page helpful?
0 / 5 - 0 ratings