Sessions
Summary
An Astro.session primitive, with pluggable storage backends.
---
// src/components/CartButton.astro
const cart = await Astro.session.get('cart');
---
<a href="/checkout">đź›’ {cart?.length ?? 0} items</a>
// src/pages/api/addToCart.ts
import type { APIContext } from "astro";
export async function POST(req: Request, context: APIContext) {
const cart = await context.session.get("cart");
cart.push(req.body.item);
await Astro.session.set("cart", cart);
return Response.json(cart);
}
Links
Some suggestions from the API bash:
session.flash(): save something to the session just for the next request- TTL in session metadata, and a
session.gc()to clean old entries --forcein CLI clears the session
My concern is related to testing fs and running the wrk benchmark tool, as I discovered that my folder became filled with a large number of new sessions, potentially making the system vulnerable to running out of disk space.
Is there a way to limit the folder/disk size allocated for storing sessions?
are there any plans for adding built-in support for session signing and verification, similar to Remix's implementation. This would help protect against session tampering and provide developers with secure defaults.
Key considerations:
- session signing: a mechanism to cryptographically sign session data, ensuring that the session hasn't been modified client-side
- verification process: add automatic verification of session signatures before processing session data key management: provide a way to configure signing keys
@bkyerv no, I hadn't though of that. I'm not sure of the threat model though. It's never exposed to the client, so I'm not sure where it could be tampered with.
@wildfiremedia this doesn't seem to be an option in the unstorage fs driver. It might be worth contributing, or at least making a feature request. It supports an in-memory LRU driver, but not anything fs-backed.
I discovered that when I try to regenerate the session, having one opened(I see the session in cookies) the session id does not change. Looking at the code, I saw that the function that generate a new id, try to obtain it first from cookie. Is this the intended behaviour or should the id be regenerated even if the session is opened?
are there any plans for adding built-in support for session signing and verification, similar to Remix's implementation. This would help protect against session tampering and provide developers with secure defaults.
Key considerations:
* session signing: a mechanism to cryptographically sign session data, ensuring that the session hasn't been modified client-side * verification process: add automatic verification of session signatures before processing session data key management: provide a way to configure signing keys
This is a server side session implementation, so there's no session data on the client that can be tampered with. The only thing you could sign would be the session ID itself, which some sessions implementations do (such as express-session). It's not really necessary since the key is always validated against the server side database (as long as you aren't vulnerable to timing-based attacks).
A feature I would like to see is the ability to use custom session read queries. For KV stores, it isn't super relevant, but if you are using an general purpose DB (e.g., postgres), it's great to have the session request include a join to also get the user data (if the session has a user ID) in a single query.
Hey everyone. Trying to use session but with a custom driver, ala https://unstorage.unjs.io/guide/custom-driver however I am getting this error:
[config] Astro found issue(s) with your configuration:
! experimental.session.driver: Expected type "string", received "object"
It seems like Astro is specifying that only a string is passed, that makes it impossible to pass in a customer driver such as:
session: {
driver: createStorage({
driver: myCustomStorageDriver({
... options here
}),
}),
}
If this only a TypeScript type check, could it be relaxed to any? Thanks!
@robertsosinski this isn't a typing issue, it's to do with the architecture: there's no way to pass the actual storage object to the runtime. The way to do this is to make the driver the import for your custom driver, so something like:
session: {
driver: "./lib/myCustomStorage",
options: {
//..options here
}
}
Will this allow for things like per-session tabs?
We currently use the old-school method of sessions at work with our point of sale, the way PHP did it in the dark days where the session ID is a part of the URL, this means our stores are able to have multiple tabs open on each terminal.
In the future I would love to move to Astro, but I fear we'll lose that ability, it isn't really a problem as far as I am concerned, but pushback can be real at times, some will adapt, others will complain for the sake of complaining.
For this use case you'd need to build something using session storage on top of the per-browser-profile sessions. If you gave every new tab its own UUID (or similar) when it is created, you could use that to key into per-tab storage in the overall session.
Session storage I can do, I guess where my confusion lies is how to utilise the browsers session storage, I assume I would do that using the islands feature?
if you want to dynamically render the page based on a per-tab ID, then it still has to be something sent with the page request and unique to the tab. which basically means a url param still, unfortunately. it can be a part of the root path, like /0/rest/of/the/path, which is how google handles multiple accounts.
rather than the session ID in the url param, it should be a secondary ID, and probably can be a number or short string. that will then be compared to the set of secondary IDs that are in the server side session. so that way the session is still secure from XSS attacks (handled with http-only secure cookies), but you can have still have per-tab identities. this should also be how you manage it with PHP too.
anything else that needs to use browser storage or in-memory values would have to make a secondary request from JS in the page. if functioning without JS is important, those solutions won't work. if initial page load performance is very important for whatever is rendered based on the per-tab ID, you also wouldn't want to wait for the multiple requests required by this approach.
Thankfully speed isn't an issue, my existing system is already blazingly fast.
We also don't currently use PHP, it's just an old-school system from the company that makes the database we use.
Was trying to get away from stuff in the URL as that can lead to stale sessions, but if I can't, then I guess I can't.
You could in theory write middleware to replace the cookies with URL params and add the params to all internal links, but it would be quite a complicated thing. I don't see us ever adding it to the core.
Getting around the 4KB cookie limit is not all that hard, Auth.js solved it a while ago: https://github.com/nextauthjs/next-auth/pull/3101
I'm trying to set up a similar concept for React Router sessions, dividing JWT across several cookies in order to keep session management simple and relying solely on data that is already available in each request instead of requiring another storage solution (memory, lru-cache, redis, etc).
Seems like a chunked cookie solution should be the default for session management; why not?
@Nettsentrisk for a very good reason! stateless authentication makes it impossible to invalidate sessions or ensure a session is logged out. making it so you can requires server side state, and if you take the refresh token approach, it's more complicated than the simpler approach of server side sessions.
server side sessions are the simplest general solution that covers all basic security needs.
@Nettsentrisk for a very good reason! stateless authentication makes it impossible to invalidate sessions or ensure a session is logged out. making it so you can requires server side state, and if you take the refresh token approach, it's more complicated than the simpler approach of server side sessions.
server side sessions are the simplest general solution that covers all basic security needs.
Those are more advanced features (invalidating sessions immediately at the solution-level), and requires an extra element of infrastructure (server-based storage connected to sessions). Auth.js lists it as one of two main strategies they provide for, there's advantages and disadvantages to both. I don't see why Astro should not provide the simplest of the two as the default strategy, where you could opt in to the more complex one if you have a database to keep track of sessions.
I haven’t looked at the code recently, but most of the effort in creating a session abstraction like this is coupled to the storage and updating of the server side information. If you want to implement a fully stateless session management with a JWT, I imagine the best way to do so would be to add middleware that does the serialization/deserialization into a cookie or auth header.
While stateless auth has its benefits, I disagree that it is the “simplest” strategy - session revocation before expiration is very important, and can cause hard to recognize security risks if not properly considered. A server side single store of truth for sessions is the most robust mechanism at the scale of most Astro sites, and I wouldn’t want new users to have to fully understand or account for the complexities of stateless auth when spinning up their sites.
I see that this proposal doesn't have auth as a goal for the implementation, only to provide session data for a user, regardless of authentication/security.
So this is really the thing I was wondering about:
We have encountered the limitations of cookie storage when building Astro Actions, where we need response data to persist between page redirects. It is easy to reach the 4kB limit in these cases.
This limitation can be fixed by chunking the session across cookies, thus removing the need to involve databases etc. simply to store such simple session data. When it's a non-goal for this session management to be connected to auth, I don't quite see why the mentioned security concerns about stateless sessions is relevant, seeing as how it's not even directly being connected to a user being logged in or authenticated.
Thus, if the primary motivation was to circumvent the cookie size limitation, then that can be fixed most simply infrastructure-wise with a chunked cookie approach.
I see that this proposal doesn't have auth as a goal for the implementation, only to provide session data for a user, regardless of authentication/security.
this is interesting as it's a pretty good point. in a vacuum, i'd agree, cookie chunking would be a reasonable solution and probably the simplest.
however, even if the design at this level is intended to be general purpose, authentication is one of the most common use cases for session storage, and it would be irresponsible to design a general purpose sessions library without taking that into account. people will use this for authentication, even if it said "DON'T USE THIS FOR AUTHENTICATION" on it, which it doesn't, the proposal actually specifically calls out auth as something this is expected to be used for.
The main reason I've not implemented a cookie-based fallback is that if you want to use cookies you can just use Astro.cookie. Making it the default would be quite confusing. I'm not sure if handling chunking automatically is reason enough to add support.
We're hoping to release this as stable in the next minor, so I'm making this a call for consensus