-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
sessions #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I suppose it doesn't even really need to be That way you could do <script>
import { stores } from '$tbd';
const { session } = stores();
let name;
async function update_username(name) {
// optimistic — update the client-side store, then persist
// (rolls back in case of failure)
+ $session.user.name = name;
- session.update($session => ({
- ...$session,
- user: { ...$session.user, name }
- }));
-
- session.persist();
// pessimistic — wait until success before updating
// client-side store
- session.persist($session => {
+ session.update($session => {
...$session,
user: { ...$session.user, name }
- });
+ }, { optimistic: false });
}
</script>
<!-- pretend i did this properly, with a progressively enhanced <form> -->
<input bind:value={name}>
<button on:click={() => update_username(name)}>
Update
</button> (if we did that, we'd probably want to export |
To persist (🙄) my comments from chat: This doesn't immediately seem like a silly idea. Having the server call Perhaps We'd want to be able to only include the client-side code for handling this if the app specifies a server-side |
I'm not really following the example. The first bit of code looks like a I think the biggest issue with |
@benmccann
It's a writable store! If someone doesn't want it to trigger reactivity then... they shouldn't write to it :) |
I'm curious about this. Like @Conduitry said, it's probably not a bad idea. I could go either way. On the one hand, implementing session behavior might arguably be something someone should do as part of setting up auth or something related, and not necessarily a part of SvelteKit. On the other hand, it's also a frequently enough employed use case that having a blessed way of handling it is probably a good idea. Agree about either the update or get approach. Not sure which I agree with. Probably need to see more implementation. 😆 |
It would be helpful to think about this in terms of which use cases it would support. The Sapper docs and the examples above only talk about storing the current user, but if that's the main use case maybe there should be more specialized support for authentication/authorization instead? You could imagine using this for storing settings (e.g. dark mode) you want available on the server side, but as soon as you have authentication you'd want to store the settings with the user rather than the session. What other use cases are there? A couple of other aspects:
|
Authentication definitely needs to be made as simple as possible, because at present it's a nightmare (not just in Sapper apps, but generally). I don't know that it can be solved at the framework level though without introducing a lot of opinions. I think the best we can do is provide a flexible enough API that it's easy to plug in packages that deal with authentication.
Shopping carts? I'm new to the site, browsing as a guest, I want my cart to persist even though I haven't registered/logged in yet: export async function get(headers) {
const cookies = cookie.parse(headers['cookie']);
const user = await db.get_user(cookies.session_id);
const cart = await (user ? db.get_cart_for_user(user.id) : db.get_cart_for_guest(cookies.session_id));
return {
user: user && {
// only expose public info
name: user.name,
email: user.email,
avatar: user.avatar
},
cart
};
} Of course you could have a
Can you elaborate? This is just how auth works, no?
I don't think it need be solved at the framework level — I'm imagining that the implementation of This is a bit of a tangent, but: speaking of shopping carts, one of my bugbears with a lot of ecommerce sites is that I often want to look at products in multiple tabs, which means my cart usually gets out of sync between them. Is there a case where you wouldn't want sessions to be synchronised across tabs with This is also tangential to
|
Yes, but authentication always times out. If we time out the session ID within a short period then, yes, I think we'd be as safe. We would also need to make sure the session ID has very high entropy and so cannot be guessed. We might also want to block mass requests for different IDs fishing for one that is valid. But I'm no security expert; someone who is should look at it, if we build something someone might store personal data with.
I still think the problem is that if the user is logged in, the data should be associated with the user rather than the session. So you'd want some way to move the data from a non-logged in session to a logged in user. Maybe there could be a
Maybe; imagine a notes application where you store new drafts of notes in the session. Then you start two new, different drafts in different tabs. Maybe far-fetched, and of course you could work around it even with a synchronized session. |
This feature wouldn't make the framework any more responsible for the actual session handling than Sapper already is - which is pretty much none. Sapper already assumes you have some mechanism for handling that, and you just give Sapper a function for deriving what it should consider the session data from the What this proposed feature is suggesting is just a slightly automated way to have an endpoint that's responsible for updates to this session data. What you write in the server-side Sapper isn't and sveltekit wouldn't be responsible for anything about session storage or the unique keys or cleaning up or anything. |
@Rich-Harris I assume the people who use JWT (and not cookies) could just rig it up a different way? (I use both.) |
Yep — at present
Your own @arxpoetica not totally sure I follow? JWT implies manually adding an // src/session/index.js
import { parse } from 'cookie';
import * as db from './db.js';
export function get({ cookie }) {
const cookies = parse(cookie);
return db.get_user(cookies.sid);
} |
At work, we have a Sapper app that is single-instance multi-tenant, and it receives information about the tenant for this request via custom HTTP headers that are injected by another service that requests run through first - and I'm handling this in Sapper by exposing those headers in the I feel like I'm also slightly against saying what's provided to user code for sessions/endpoints/etc needs to be a common denominator of what's possible in all conceivable deployment environments or adapters. I'm not sure what this means for the API, but if I have no intention of ever deploying to AWS Lambdas and I am inconvenienced by an API limitation that exists in all environments because of AWS Lambdas, that's going to be irritating. Combining these two points, I don't currently have a situation in mind where having access to the HTTP headers (for session) would be insufficient, or where having access to HTTP headers and a readable stream (for endpoints) would be insufficient - but I'm concerned about a situation where someone only ever intends to build a Node server and really just wants to be able to access If there's another issue where these conversations should happen, I'm happy to move over there. |
Since this has turned into a more general thread about session handling, I think this is a fine venue for a conversation about cookies vs all headers etc. And I'm glad you brought up the readable stream thing as I'm about to open a separate issue about body parsing. In a more general sense, I think it's important to be future-proof, but if that means potentially having different APIs for different platforms then there's a pretty significant cost to that. So I think we should limit our design to the simplest thing that meets the use cases we can imagine, while being as imaginative as possible. (We can always break things in a future version if we really have to.) The one thing the code examples above don't illustrate is setting a |
Just realised there's a big gap in all the code above: endpoints are likely to need data that shouldn't be public. For example, an endpoint might need a user's access token to communicate with some third party service on their behalf, and that token shouldn't be sent to the client. (In the case of svelte.dev, we store GitHub access tokens in our session database.) So an endpoint signature like this... export function get({ params }, session) {
// ...
} ...doesn't make sense, or at least The point at which the context is generated is probably the right place to add a // src/setup/index.js
import { parse } from 'cookie';
import uuid from '@lukeed/uuid';
import * as db from './db.js';
export async function prepare(request_headers) {
const cookies = parse(request_headers['cookie']);
const response_headers = {
'x-foo': 'potato'
};
if (!cookies.sid) {
response_headers['set-cookie'] = `sid=${uuid()}; HttpOnly`;
}
return {
context: {
user: (await db.get_user(cookies.sid)) || { guest: true }
},
headers: response_headers
};
}
export function getSession(context) {
return {
user: {
// strip out access tokens and what-have-you
id: context.user.id,
name: context.user.name,
avatar: context.user.avatar,
guest: context.user.guest
}
};
}
export async function setSession(context, session) {
// we could get the previous session with getSession if we were so inclined
await db.set_user(context.user.id, session.user);
} I would love for someone to tell me a) if this is a sensible design and b) what to call everything |
Have gone ahead and implemented
|
I have a design for |
Going to close this in favour of #1726 as much of the thread is rather out of date |
Something people seem to trip over a bit is the fact that
session
, despite being a writable store, doesn't get persisted. I wonder if we can address that:This requires that the developer add some persistence logic. Perhaps in the same file where we define logic for getting a session (#9), we have a place to put logic for persistence:
Glossing over some details but what do folks think?
The text was updated successfully, but these errors were encountered: