|
2 | 2 | title: "Sessions" |
3 | 3 | --- |
4 | 4 |
|
5 | | -When user information is required, it is usually done by checking the request for information. |
6 | | -The best way for the client and server to do that is using cookies. |
| 5 | +Sessions allow web applications to maintain state between user requests. |
| 6 | +Since HTTP is stateless, each request is treated independently. |
| 7 | +Sessions address this by allowing the server to recognize multiple requests from the same user, which is helpful for tracking authentication and preferences. |
7 | 8 |
|
8 | | -The `Request` object can be used to access the `Cookie` Headers, which can then be parsed to get the value for that specific cookie. |
9 | | -For example, `"session"` can be used to identify the session. |
10 | | -Fortunately, Nitro comes ready with helpers that enable this. |
| 9 | +## How sessions work |
11 | 10 |
|
12 | | -For example, if you wanted to use a cookie to identify a user, you can use the `useSession` helper from `vinxi/http`: |
| 11 | +A session typically involves: |
13 | 12 |
|
14 | | -```tsx title="/lib/session.ts" |
| 13 | +1. **Session creation**: When tracking is needed (e.g., upon login or first visit), the server creates a session. |
| 14 | + This involves generating a unique **session ID** and storing the session data, _encrypted and signed_, within a cookie. |
| 15 | +2. **Session cookie transmission**: The server sends a `Set-Cookie` HTTP header. |
| 16 | + This instructs the browser to store the session cookie. |
| 17 | +3. **Subsequent requests**: The browser automatically includes the session cookie in the `Cookie` HTTP header for requests to the server. |
| 18 | +4. **Session retrieval and data access**: For each request, the server checks for the session cookie, retrieves the session data if a cookie is present, then decrypts and verifies the signature of the session data for the application to access and use this data. |
| 19 | +5. **Session expiration and destruction**: Sessions typically expire after a period of time or upon user sign-out and the data is removed. |
| 20 | + This is done by setting a `Max-Age` attribute on the cookie or by sending a `Set-Cookie` HTTP header with an expired date. |
| 21 | + |
| 22 | +Most of these steps are automatically managed by the [session helpers](#session-helpers). |
| 23 | + |
| 24 | +### Database sessions |
| 25 | + |
| 26 | +For larger applications or when more robust session management is required, SolidStart also supports storing session data in a database. |
| 27 | +This approach is similar to the cookie-based approach, but with some key differences: |
| 28 | + |
| 29 | +- The session data is stored in the database, associated with the session ID. |
| 30 | +- Only the session ID is stored in the cookie, not the session data. |
| 31 | +- The session data is retrieved from the database using the session ID, instead of being retrieved directly from the cookie. |
| 32 | +- Upon expiration, in addition to the session cookie, the database record containing the session data is also removed. |
| 33 | + |
| 34 | +SolidStart does not automatically handle interactions with a database; you need to implement this yourself. |
| 35 | + |
| 36 | +## Session helpers |
| 37 | + |
| 38 | +[Vinxi](https://vinxi.vercel.app), the underlying server toolkit powering SolidStart, provides helpers to simplify working with sessions. |
| 39 | +It provides a few key session helpers: |
| 40 | + |
| 41 | +- [`useSession`](https://vinxi.vercel.app/api/server/session.html#usesession): Initializes a session or retrieves the existing session and returns a session object. |
| 42 | +- [`getSession`](https://vinxi.vercel.app/api/server/session.html#getsession): Retrieves the current session or initializes a new session. |
| 43 | +- [`updateSession`](https://vinxi.vercel.app/api/server/session.html#updatesession): Updates data within the current session. |
| 44 | +- [`clearSession`](https://vinxi.vercel.app/api/server/session.html#clearsession): Clears the current session. |
| 45 | + |
| 46 | +These helpers work _only_ in server-side contexts, such as within server functions and API routes. |
| 47 | +This is because session management requires access to server-side resources as well as the ability to get and set HTTP headers. |
| 48 | + |
| 49 | +For more information, see the [Cookies documentation in the Vinxi docs](https://vinxi.vercel.app/api/server/session.html). |
| 50 | + |
| 51 | +## Creating a session |
| 52 | + |
| 53 | +The `useSession` helper is the primary way to create and manage sessions. |
| 54 | +It provides a comprehensive interface for all session operations. |
| 55 | + |
| 56 | +```ts title="src/lib/session.ts" |
15 | 57 | import { useSession } from "vinxi/http"; |
16 | | -export async function getUser(request: Request) { |
17 | | - const session = await useSession({ |
18 | | - password: process.env.SESSION_SECRET |
19 | | - }); |
| 58 | + |
| 59 | +type SessionData = { |
| 60 | + theme: "light" | "dark"; |
| 61 | +}; |
| 62 | + |
| 63 | +export async function useThemeSession() { |
| 64 | + "use server"; |
| 65 | + const session = await useSession<SessionData>({ |
| 66 | + password: process.env.SESSION_SECRET as string, |
| 67 | + name: "theme", |
| 68 | + }); |
| 69 | + |
| 70 | + if (!session.data.theme) { |
| 71 | + await session.update({ |
| 72 | + theme: "light", |
| 73 | + }); |
| 74 | + } |
| 75 | + |
| 76 | + return session; |
20 | 77 | } |
21 | 78 | ``` |
22 | 79 |
|
23 | | -The session cookie can be used to get the session data about the request. |
24 | | -How the session data is stored and retrieved, however, is up to the implementation of the `useSession`. |
| 80 | +In this example, the `useThemeSession` server function creates a session that stores a user's theme preference. |
25 | 81 |
|
26 | | -Typically, `userId` will be saved in the session data and if it is not found, it indicates that the request was not authenticated. |
27 | | -The `getUser` function returns a `null` when it does not find a user and if a user is found, it will be used to get the user from the database: |
| 82 | +`useSession` requires a strong password for encrypting and signing the session cookie. |
| 83 | +This password must be at least 32 characters long and should be kept highly secure. |
| 84 | +It is strongly recommended to store this password in a [private environment variable](/configuration/environment-variables#private-environment-variables), as shown in the example above, rather than hardcoding it in your source code. |
28 | 85 |
|
29 | | -```tsx title="/lib/session.ts" |
30 | | -import { useSession } from "vinxi/http"; |
| 86 | +A password can be generated using the following command: |
31 | 87 |
|
32 | | -export async function getUser(): Promise<User | null> { |
33 | | - const session = await useSession({ |
34 | | - password: process.env.SESSION_SECRET |
35 | | - }); |
36 | | - const userId = session.data.userId; |
37 | | - if (!userId) return null; |
38 | | - return await store.getUser(userId); |
39 | | -} |
| 88 | +```sh frame="none" |
| 89 | +openssl rand -base64 32 |
40 | 90 | ``` |
41 | 91 |
|
42 | | -This helper can be used wherever you want to authenticate the request, including in server functions and [API routes](/solid-start/building-your-application/api-routes). |
| 92 | +`useSession` adds a `Set-Cookie` HTTP header to the current server response. |
| 93 | +By default, the cookie is named `h3`, but can be customized with the `name` option, as shown in the example above. |
43 | 94 |
|
44 | | -Additionally, you can use it with [`query`](/solid-router/reference/data-apis/query) from `solid-router` to make sure that only authenticated users can access the data. |
45 | | -That way if the user is not authenticated, the request will be redirected to the login page. |
| 95 | +## Getting the session data |
46 | 96 |
|
47 | | -```tsx title="/routes/api/store/admin.ts" |
48 | | -import { query, createAsync, redirect } from "@solidjs/router"; |
| 97 | +The `useSession` helper provides access to the session data from the current request with the `data` property. |
49 | 98 |
|
50 | | -const getUsers = query(async (id: string) => { |
51 | | - "use server"; |
52 | | - const user = await getUser(); |
53 | | - if (!user) throw redirect("/login"); |
54 | | - return store.getUsers(id, "*"); |
55 | | -}, "users"); |
| 99 | +```ts title="src/lib/session.ts" |
| 100 | +export async function getThemeSession() { |
| 101 | + "use server"; |
| 102 | + const session = await useThemeSession(); |
56 | 103 |
|
57 | | -// page component |
58 | | -export default function Users() { |
59 | | - const users = createAsync(() => getUsers()); |
| 104 | + return session.data.theme; |
60 | 105 | } |
61 | 106 | ``` |
62 | 107 |
|
63 | | -This also allows logging in and out of the session in a similar manner: |
| 108 | +## Updating the session data |
64 | 109 |
|
65 | | -```tsx title="/routes/session.server.ts" |
66 | | -import { redirect } from "@solidjs/router"; |
67 | | -import { useSession } from "vinxi/http"; |
| 110 | +The `useSession` helper provides the `update` method to update the session data from the current request. |
68 | 111 |
|
69 | | -type UserSession = { |
70 | | - userId?: number; |
71 | | -}; |
72 | | - |
73 | | -function getSession() { |
74 | | - return useSession({ |
75 | | - password: process.env.SESSION_SECRET |
76 | | - }); |
| 112 | +```ts title="src/lib/session.ts" |
| 113 | +export async function updateThemeSession(data: SessionData) { |
| 114 | + "use server"; |
| 115 | + const session = await useThemeSession(); |
| 116 | + await session.update(data); |
77 | 117 | } |
| 118 | +``` |
78 | 119 |
|
79 | | -export async function login(formData: FormData) { |
80 | | - const username = String(formData.get("username")); |
81 | | - const password = String(formData.get("password")); |
82 | | - // do validation |
83 | | - try { |
84 | | - const session = await getSession(); |
85 | | - const user = await db.user.findUnique({ where: { username } }); |
86 | | - if (!user || password !== user.password) return new Error("Invalid login"); |
87 | | - await session.update((d: UserSession) => (d.userId = user!.id)); |
88 | | - } catch (err) { |
89 | | - return err as Error; |
90 | | - } |
91 | | - throw redirect("/"); |
92 | | -} |
| 120 | +## Clearing the session data |
| 121 | + |
| 122 | +The `useSession` helper provides the `clear` method to clear the session data from the current request. |
93 | 123 |
|
94 | | -export async function logout() { |
95 | | - const session = await getSession(); |
96 | | - await session.update((d: UserSession) => (d.userId = undefined)); |
97 | | - throw redirect("/login"); |
| 124 | +```ts title="src/lib/session.ts" |
| 125 | +export async function clearThemeSession() { |
| 126 | + "use server"; |
| 127 | + const session = await useThemeSession(); |
| 128 | + await session.clear(); |
98 | 129 | } |
99 | | -``` |
| 130 | +``` |
0 commit comments