iron-session 7.0.6 → 8.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +20 -0
- package/README.md +364 -632
- package/dist/index.cjs +785 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +230 -0
- package/dist/index.d.ts +204 -40
- package/dist/index.js +722 -136
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +791 -0
- package/dist/index.node.cjs.map +1 -0
- package/dist/index.node.js +776 -0
- package/dist/index.node.js.map +1 -0
- package/package.json +68 -170
- package/LICENSE +0 -21
- package/dist/index.mjs +0 -159
- package/dist/index.mjs.map +0 -1
- package/express/dist/index.d.ts +0 -6
- package/express/dist/index.js +0 -61
- package/express/dist/index.js.map +0 -1
- package/express/dist/index.mjs +0 -37
- package/express/dist/index.mjs.map +0 -1
- package/express/index.d.ts +0 -6
- package/express/index.js +0 -61
- package/express/index.js.map +0 -1
- package/express/index.mjs +0 -37
- package/express/index.mjs.map +0 -1
- package/express/index.test.ts +0 -41
- package/express/index.ts +0 -19
- package/express/tsconfig.json +0 -8
- package/next/dist/index.d.ts +0 -14
- package/next/dist/index.js +0 -82
- package/next/dist/index.js.map +0 -1
- package/next/dist/index.mjs +0 -57
- package/next/dist/index.mjs.map +0 -1
- package/next/index.d.ts +0 -14
- package/next/index.js +0 -82
- package/next/index.js.map +0 -1
- package/next/index.mjs +0 -57
- package/next/index.mjs.map +0 -1
- package/next/index.test.ts +0 -253
- package/next/index.ts +0 -87
- package/next/tsconfig.json +0 -8
package/README.md
CHANGED
|
@@ -1,763 +1,495 @@
|
|
|
1
|
-
# iron-session
|
|
1
|
+
# iron-session
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A runtime-agnostic stateless session utility. Works with Next.js, Node.js, Deno,
|
|
4
|
+
Bun, and more.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
The session data is stored in signed and encrypted cookies ("seals") which can
|
|
7
|
+
only be decoded by your server. There are no session ids, making sessions
|
|
8
|
+
"stateless" from the server point of view. This strategy of storing session data
|
|
9
|
+
is the same technique used by frameworks like
|
|
10
|
+
[Ruby On Rails](https://guides.rubyonrails.org/security.html#session-storage).
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
## Table of Contents
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
The underlying cryptography library is [iron](https://hapi.dev/module/iron) which was [created by the lead developer of OAuth 2.0](https://hueniversedotcom.wordpress.com/2015/09/19/auth-to-see-the-wizard-or-i-wrote-an-oauth-replacement/).
|
|
12
|
-
|
|
13
|
-
<p align="center"><b>Online demo: <a href="https://iron-session-example.vercel.app/">https://iron-session-example.vercel.app</a> 👀</b></p>
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
_Table of contents:_
|
|
14
|
+
<!-- - [Features](#features) -->
|
|
18
15
|
|
|
19
16
|
- [Installation](#installation)
|
|
20
|
-
- [Usage
|
|
21
|
-
- [
|
|
22
|
-
- [Coding best practices](#coding-best-practices)
|
|
23
|
-
- [Session wrappers](#session-wrappers)
|
|
24
|
-
- [Typing session data with TypeScript](#typing-session-data-with-typescript)
|
|
25
|
-
- [Express](#express)
|
|
26
|
-
- [Handle password rotation/update the password](#handle-password-rotationupdate-the-password)
|
|
27
|
-
- [Magic links](#magic-links)
|
|
28
|
-
- [Impersonation, login as someone else](#impersonation-login-as-someone-else)
|
|
29
|
-
- [Session cookies](#session-cookies)
|
|
30
|
-
- [Firebase usage](#firebase-usage)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Options Definitions](#options-definitions)
|
|
31
19
|
- [API](#api)
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [session.save()](#sessionsave)
|
|
37
|
-
- [session.destroy()](#sessiondestroy)
|
|
20
|
+
- [Iron Session Object](#iron-session-object)
|
|
21
|
+
- [Functions](#iron-session-functions)
|
|
22
|
+
- [Options](#iron-session-options)
|
|
23
|
+
- [Example](#nextjs-example)
|
|
38
24
|
- [FAQ](#faq)
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [How is this different from JWT?](#how-is-this-different-from-jwt)
|
|
42
|
-
- [Project status](#project-status)
|
|
25
|
+
<!-- - [Contributing](#contributing) -->
|
|
26
|
+
<!-- - [License](#license) -->
|
|
43
27
|
- [Credits](#credits)
|
|
44
|
-
- [
|
|
45
|
-
- [Contributors](#contributors)
|
|
28
|
+
- [Good Reads](#good-reads)
|
|
46
29
|
|
|
47
30
|
## Installation
|
|
48
31
|
|
|
49
|
-
```
|
|
32
|
+
```sh
|
|
50
33
|
npm add iron-session
|
|
51
34
|
```
|
|
52
35
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
You can find full featured examples (Next.js, Express) in the [examples folder](examples).
|
|
56
|
-
|
|
57
|
-
The password is a private key you must pass at runtime and builtime (for getServerSideProps), it has to be at least 32 characters long. You can use https://1password.com/password-generator/ to generate strong passwords.
|
|
58
|
-
|
|
59
|
-
Session duration is 14 days by default, check the API docs for more info.
|
|
60
|
-
|
|
61
|
-
⚠️ Always store passwords in encrypted environment variables on your platform. Vercel does this automatically.
|
|
62
|
-
|
|
63
|
-
**Login API Route:**
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
// pages/api/login.ts
|
|
67
|
-
|
|
68
|
-
import { withIronSessionApiRoute } from "iron-session/next";
|
|
69
|
-
|
|
70
|
-
export default withIronSessionApiRoute(
|
|
71
|
-
async function loginRoute(req, res) {
|
|
72
|
-
// get user from database then:
|
|
73
|
-
req.session.user = {
|
|
74
|
-
id: 230,
|
|
75
|
-
admin: true,
|
|
76
|
-
};
|
|
77
|
-
await req.session.save();
|
|
78
|
-
res.send({ ok: true });
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
cookieName: "myapp_cookiename",
|
|
82
|
-
password: "complex_password_at_least_32_characters_long",
|
|
83
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
84
|
-
cookieOptions: {
|
|
85
|
-
secure: process.env.NODE_ENV === "production",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**User API Route:**
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
// pages/api/user.ts
|
|
36
|
+
Change the package manager to whatever you use, of course. On Deno, you can use
|
|
37
|
+
[esm.sh](https://esm.sh/):
|
|
95
38
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
export default withIronSessionApiRoute(
|
|
99
|
-
function userRoute(req, res) {
|
|
100
|
-
res.send({ user: req.session.user });
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
cookieName: "myapp_cookiename",
|
|
104
|
-
password: "complex_password_at_least_32_characters_long",
|
|
105
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
106
|
-
cookieOptions: {
|
|
107
|
-
secure: process.env.NODE_ENV === "production",
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
);
|
|
39
|
+
```js
|
|
40
|
+
import { getIronSession } from 'https://esm.sh/iron-session@latest'
|
|
111
41
|
```
|
|
112
42
|
|
|
113
|
-
|
|
43
|
+
## Usage
|
|
114
44
|
|
|
115
|
-
|
|
116
|
-
// pages/api/logout.ts
|
|
45
|
+
Refer to the [examples](examples).
|
|
117
46
|
|
|
118
|
-
|
|
47
|
+
1. Define your session options
|
|
48
|
+
1. Initialize your session with:
|
|
49
|
+
- sessionOptions and the respective parameters
|
|
50
|
+
- the type definition of your session data
|
|
51
|
+
1. Set the session data variables
|
|
52
|
+
1. Set the data to or read the data from the browser cookie storage
|
|
53
|
+
- session.save(): Set the session variables as an encrypted string to the browser cookie storage
|
|
54
|
+
- session.destroy(): Set cookie value in the browser cookie storage as an empty value to clear the cookie data
|
|
55
|
+
- Read the session variables by decrypting the encrypted string from the cookie browser storage
|
|
119
56
|
|
|
120
|
-
|
|
121
|
-
function logoutRoute(req, res, session) {
|
|
122
|
-
req.session.destroy();
|
|
123
|
-
res.send({ ok: true });
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
cookieName: "myapp_cookiename",
|
|
127
|
-
password: "complex_password_at_least_32_characters_long",
|
|
128
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
129
|
-
cookieOptions: {
|
|
130
|
-
secure: process.env.NODE_ENV === "production",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
```
|
|
57
|
+
## Options Definitions
|
|
135
58
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
// pages/admin.tsx
|
|
140
|
-
|
|
141
|
-
import { withIronSessionSsr } from "iron-session/next";
|
|
142
|
-
|
|
143
|
-
export const getServerSideProps = withIronSessionSsr(
|
|
144
|
-
async function getServerSideProps({ req }) {
|
|
145
|
-
const user = req.session.user;
|
|
59
|
+
Only two options are required: `password` and `cookieName`. Everything else is automatically computed and usually doesn't need to be changed.
|
|
146
60
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
61
|
+
- `password`, **required**: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use <https://1password.com/password-generator/> to generate strong passwords. `password` can be either a `string` or an `array` of objects like this: `[{id: 2, password: "..."}, {id: 1, password: "..."}]` to allow for password rotation.
|
|
62
|
+
- `cookieName`, **required**: Name of the cookie to be stored
|
|
63
|
+
- `ttl`, _optional_: In seconds. Default to the equivalent of 14 days. You can set this to `0` and iron-session will compute the maximum allowed value by cookies (~70 years).
|
|
64
|
+
- `cookieOptions`, _optional_: Any option available from [jshttp/cookie#serialize](https://github.com/jshttp/cookie#cookieserializename-value-options) except for `encode` which is not a Set-Cookie Attribute. See [Mozilla Set-Cookie Attributes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes) and [Chrome Cookie Fields](https://developer.chrome.com/docs/devtools/application/cookies/#fields). Default to:
|
|
152
65
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
},
|
|
167
|
-
);
|
|
66
|
+
```js
|
|
67
|
+
{
|
|
68
|
+
httpOnly: true,
|
|
69
|
+
secure: true, // true when using https, false otherwise
|
|
70
|
+
sameSite: "lax", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
|
|
71
|
+
// The next line makes sure browser will expire cookies before seals are considered expired by the server. It also allows for clock difference of 60 seconds maximum between servers and clients.
|
|
72
|
+
maxAge: (ttl === 0 ? 2147483647 : ttl) - 60,
|
|
73
|
+
path: "/",
|
|
74
|
+
// other options:
|
|
75
|
+
// domain, if you want the cookie to be valid for the whole domain and subdomains, use domain: example.com
|
|
76
|
+
// expires, there should be no need to use this option, maxAge takes precedence
|
|
77
|
+
// ...
|
|
78
|
+
}
|
|
168
79
|
```
|
|
169
80
|
|
|
170
|
-
|
|
81
|
+
The final type definition for `CookieOptions` ends up to be
|
|
171
82
|
|
|
172
|
-
|
|
83
|
+
`'domain' | 'path' | 'secure' | 'sameSite' | 'name' | 'value' | 'expires' | 'httpOnly' | 'maxAge' | 'priority'`
|
|
173
84
|
|
|
174
|
-
###
|
|
85
|
+
### Type Definitions from iron-session/dist/index.node.d.cts
|
|
175
86
|
|
|
176
|
-
|
|
87
|
+
#### IronSessionOptions
|
|
177
88
|
|
|
178
89
|
```ts
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
90
|
+
interface IronSessionOptions {
|
|
91
|
+
/**
|
|
92
|
+
* The cookie name that will be used inside the browser. Make sure it's unique
|
|
93
|
+
* given your application.
|
|
94
|
+
*
|
|
95
|
+
* @example 'vercel-session'
|
|
96
|
+
*/
|
|
97
|
+
cookieName: string;
|
|
98
|
+
/**
|
|
99
|
+
* The password(s) that will be used to encrypt the cookie. Can either be a string
|
|
100
|
+
* or an object.
|
|
101
|
+
*
|
|
102
|
+
* When you provide multiple passwords then all of them will be used to decrypt
|
|
103
|
+
* the cookie. But only the most recent (`= highest key`, `2` in the example)
|
|
104
|
+
* password will be used to encrypt the cookie. This allows password rotation.
|
|
105
|
+
*
|
|
106
|
+
* @example { 1: 'password-1', 2: 'password-2' }
|
|
107
|
+
*/
|
|
108
|
+
password: Password;
|
|
109
|
+
/**
|
|
110
|
+
* The time (in seconds) that the session will be valid for. Also sets the
|
|
111
|
+
* `max-age` attribute of the cookie automatically (`= ttl - 60s`, so that the
|
|
112
|
+
* cookie always expire before the session).
|
|
113
|
+
*
|
|
114
|
+
* `ttl = 0` means no expiration.
|
|
115
|
+
*
|
|
116
|
+
* @default 1209600
|
|
117
|
+
*/
|
|
118
|
+
ttl?: number;
|
|
119
|
+
/**
|
|
120
|
+
* The options that will be passed to the cookie library.
|
|
121
|
+
*
|
|
122
|
+
* If you want to use "session cookies" (cookies that are deleted when the browser
|
|
123
|
+
* is closed) then you need to pass `cookieOptions: { maxAge: undefined }`
|
|
124
|
+
*
|
|
125
|
+
* @see https://github.com/jshttp/cookie#options-1
|
|
126
|
+
*/
|
|
127
|
+
cookieOptions?: CookieOptions;
|
|
194
128
|
}
|
|
195
129
|
```
|
|
196
130
|
|
|
197
|
-
|
|
198
|
-
// lib/config.ts
|
|
199
|
-
|
|
200
|
-
export const ironOptions = {
|
|
201
|
-
cookieName: "myapp_cookiename",
|
|
202
|
-
password: "complex_password_at_least_32_characters_long",
|
|
203
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
204
|
-
cookieOptions: {
|
|
205
|
-
secure: process.env.NODE_ENV === "production",
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Session wrappers
|
|
211
|
-
|
|
212
|
-
If you do not want to pass down the password and cookie name in every API route file or page then you can create wrappers like this:
|
|
213
|
-
|
|
214
|
-
**JavaScript:**
|
|
215
|
-
|
|
216
|
-
```js
|
|
217
|
-
// lib/withSession.js
|
|
218
|
-
|
|
219
|
-
import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
|
|
220
|
-
|
|
221
|
-
const sessionOptions = {
|
|
222
|
-
password: "complex_password_at_least_32_characters_long",
|
|
223
|
-
cookieName: "myapp_cookiename",
|
|
224
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
225
|
-
cookieOptions: {
|
|
226
|
-
secure: process.env.NODE_ENV === "production",
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export function withSessionRoute(handler) {
|
|
231
|
-
return withIronSessionApiRoute(handler, sessionOptions);
|
|
232
|
-
}
|
|
131
|
+
##### CookieOptions
|
|
233
132
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
133
|
+
```ts
|
|
134
|
+
/**
|
|
135
|
+
* Set-Cookie Attributes do not include `encode`. We omit this from our `cookieOptions` type.
|
|
136
|
+
*
|
|
137
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
|
138
|
+
* @see https://developer.chrome.com/docs/devtools/application/cookies/
|
|
139
|
+
*/
|
|
140
|
+
type CookieOptions = Omit<CookieSerializeOptions, 'encode'>
|
|
237
141
|
```
|
|
238
142
|
|
|
239
|
-
|
|
143
|
+
##### CookieSerializeOptions
|
|
240
144
|
|
|
241
145
|
```ts
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
146
|
+
interface CookieSerializeOptions {
|
|
147
|
+
/**
|
|
148
|
+
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no
|
|
149
|
+
* domain is set, and most clients will consider the cookie to apply to only
|
|
150
|
+
* the current domain.
|
|
151
|
+
*/
|
|
152
|
+
domain?: string | undefined;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Specifies a function that will be used to encode a cookie's value. Since
|
|
156
|
+
* value of a cookie has a limited character set (and must be a simple
|
|
157
|
+
* string), this function can be used to encode a value into a string suited
|
|
158
|
+
* for a cookie's value.
|
|
159
|
+
*
|
|
160
|
+
* The default function is the global `encodeURIComponent`, which will
|
|
161
|
+
* encode a JavaScript string into UTF-8 byte sequences and then URL-encode
|
|
162
|
+
* any that fall outside of the cookie range.
|
|
163
|
+
*/
|
|
164
|
+
encode?(value: string): string;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default,
|
|
168
|
+
* no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete
|
|
169
|
+
* it on a condition like exiting a web browser application.
|
|
170
|
+
*
|
|
171
|
+
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
|
|
172
|
+
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
|
|
173
|
+
* possible not all clients by obey this, so if both are set, they should
|
|
174
|
+
* point to the same date and time.
|
|
175
|
+
*/
|
|
176
|
+
expires?: Date | undefined;
|
|
177
|
+
/**
|
|
178
|
+
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}.
|
|
179
|
+
* When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
|
|
180
|
+
* default, the `HttpOnly` attribute is not set.
|
|
181
|
+
*
|
|
182
|
+
* *Note* be careful when setting this to true, as compliant clients will
|
|
183
|
+
* not allow client-side JavaScript to see the cookie in `document.cookie`.
|
|
184
|
+
*/
|
|
185
|
+
httpOnly?: boolean | undefined;
|
|
186
|
+
/**
|
|
187
|
+
* Specifies the number (in seconds) to be the value for the `Max-Age`
|
|
188
|
+
* `Set-Cookie` attribute. The given number will be converted to an integer
|
|
189
|
+
* by rounding down. By default, no maximum age is set.
|
|
190
|
+
*
|
|
191
|
+
* *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification}
|
|
192
|
+
* states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
|
|
193
|
+
* possible not all clients by obey this, so if both are set, they should
|
|
194
|
+
* point to the same date and time.
|
|
195
|
+
*/
|
|
196
|
+
maxAge?: number | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}.
|
|
199
|
+
* By default, the path is considered the "default path".
|
|
200
|
+
*/
|
|
201
|
+
path?: string | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1].
|
|
204
|
+
*
|
|
205
|
+
* - `'low'` will set the `Priority` attribute to `Low`.
|
|
206
|
+
* - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
|
|
207
|
+
* - `'high'` will set the `Priority` attribute to `High`.
|
|
208
|
+
*
|
|
209
|
+
* More information about the different priority levels can be found in
|
|
210
|
+
* [the specification][rfc-west-cookie-priority-00-4.1].
|
|
211
|
+
*
|
|
212
|
+
* **note** This is an attribute that has not yet been fully standardized, and may change in the future.
|
|
213
|
+
* This also means many clients may ignore this attribute until they understand it.
|
|
214
|
+
*/
|
|
215
|
+
priority?: 'low' | 'medium' | 'high' | undefined;
|
|
216
|
+
/**
|
|
217
|
+
* Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}.
|
|
218
|
+
*
|
|
219
|
+
* - `true` will set the `SameSite` attribute to `Strict` for strict same
|
|
220
|
+
* site enforcement.
|
|
221
|
+
* - `false` will not set the `SameSite` attribute.
|
|
222
|
+
* - `'lax'` will set the `SameSite` attribute to Lax for lax same site
|
|
223
|
+
* enforcement.
|
|
224
|
+
* - `'strict'` will set the `SameSite` attribute to Strict for strict same
|
|
225
|
+
* site enforcement.
|
|
226
|
+
* - `'none'` will set the SameSite attribute to None for an explicit
|
|
227
|
+
* cross-site cookie.
|
|
228
|
+
*
|
|
229
|
+
* More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}.
|
|
230
|
+
*
|
|
231
|
+
* *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
|
|
232
|
+
*/
|
|
233
|
+
sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined;
|
|
234
|
+
/**
|
|
235
|
+
* Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the
|
|
236
|
+
* `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
|
|
237
|
+
*
|
|
238
|
+
* *Note* be careful when setting this to `true`, as compliant clients will
|
|
239
|
+
* not send the cookie back to the server in the future if the browser does
|
|
240
|
+
* not have an HTTPS connection.
|
|
241
|
+
*/
|
|
242
|
+
secure?: boolean | undefined;
|
|
262
243
|
}
|
|
263
244
|
|
|
264
|
-
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
|
|
265
|
-
export function withSessionSsr<
|
|
266
|
-
P extends { [key: string]: unknown } = { [key: string]: unknown },
|
|
267
|
-
>(
|
|
268
|
-
handler: (
|
|
269
|
-
context: GetServerSidePropsContext,
|
|
270
|
-
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>,
|
|
271
|
-
) {
|
|
272
|
-
return withIronSessionSsr(handler, sessionOptions);
|
|
273
|
-
}
|
|
274
245
|
```
|
|
275
246
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
// pages/api/login.ts
|
|
280
|
-
|
|
281
|
-
import { withSessionRoute } from "lib/withSession";
|
|
247
|
+
## API
|
|
282
248
|
|
|
283
|
-
|
|
249
|
+
## Iron Session Object
|
|
284
250
|
|
|
285
|
-
|
|
286
|
-
// get user from database then:
|
|
287
|
-
req.session.user = {
|
|
288
|
-
id: 230,
|
|
289
|
-
admin: true,
|
|
290
|
-
};
|
|
291
|
-
await req.session.save();
|
|
292
|
-
res.send("Logged in");
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Usage in getServerSideProps:**
|
|
251
|
+
### getIronSession(req: Request | IncomingMessage, res: Response | ServerResponse<IncomingMessage>, userSessionOptions: IronSessionOptions): Promise<IronSession<T>>
|
|
297
252
|
|
|
298
253
|
```ts
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
import { withSessionSsr } from "lib/withSession";
|
|
302
|
-
|
|
303
|
-
export const getServerSideProps = withSessionSsr(
|
|
304
|
-
async function getServerSideProps({ req }) {
|
|
305
|
-
const user = req.session.user;
|
|
306
|
-
|
|
307
|
-
if (user.admin !== true) {
|
|
308
|
-
return {
|
|
309
|
-
notFound: true,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
props: {
|
|
315
|
-
user: req.session.user,
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
},
|
|
319
|
-
);
|
|
254
|
+
const session = getIronSession<IronSessionData>(req, res, sessionOptions)
|
|
320
255
|
```
|
|
321
256
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
`req.session` is automatically populated with the right types so .save() and .destroy() can be called on it.
|
|
257
|
+
The API Route Handler that uses `getIronSession` and returns the Response needs to be called from a client-side environment (ie. a 'use client' file).
|
|
325
258
|
|
|
326
|
-
|
|
259
|
+
### getServerActionIronSession(userSessionOptions: IronSessionOptions, cookieHandler: ICookieHandler): Promise<IronSession<T>>
|
|
327
260
|
|
|
328
261
|
```ts
|
|
329
|
-
|
|
330
|
-
interface IronSessionData {
|
|
331
|
-
user?: {
|
|
332
|
-
id: number;
|
|
333
|
-
admin?: boolean;
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
}
|
|
262
|
+
const session = getServerActionIronSession<IronSessionData>(sessionOptions, cookies())
|
|
337
263
|
```
|
|
338
264
|
|
|
339
|
-
|
|
265
|
+
The `getServerActionIronSession` implementation uses the `cookies()` function from next/headers to set the cookies so that Iron Session can be used in NextJS Server Actions and React Server Components in a server-side environment (ie. a 'use server' file).
|
|
340
266
|
|
|
341
|
-
|
|
267
|
+
## Iron Session Functions
|
|
342
268
|
|
|
343
|
-
###
|
|
269
|
+
### session.save(saveOptions?: OverridableOptions)
|
|
344
270
|
|
|
345
|
-
|
|
271
|
+
Saves the session and sets the cookie header to be sent once the response is sent.
|
|
346
272
|
|
|
347
|
-
|
|
273
|
+
```ts
|
|
274
|
+
await session.save()
|
|
275
|
+
```
|
|
348
276
|
|
|
349
|
-
|
|
277
|
+
### session.destroy(destroyOptions?: OverridableOptions)
|
|
350
278
|
|
|
351
|
-
|
|
352
|
-
- change the password you previously used because it leaked somewhere (😱)
|
|
279
|
+
Empties the session object and sets the cookie header to be sent once the response is sent. The browser will then set the cookie value as an empty value.
|
|
353
280
|
|
|
354
|
-
|
|
281
|
+
```ts
|
|
282
|
+
await session.destroy()
|
|
283
|
+
```
|
|
355
284
|
|
|
356
|
-
|
|
285
|
+
Upon calling either `session.save()` or `session.destroy()` the session values are saved to the browser cookie storage.
|
|
357
286
|
|
|
358
|
-
|
|
359
|
-
withIronSessionApiRoute(handler, {
|
|
360
|
-
password: {
|
|
361
|
-
1: "complex_password_at_least_32_characters_long",
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
```
|
|
287
|
+
## Iron Session Options
|
|
365
288
|
|
|
366
|
-
|
|
289
|
+
### Default Options
|
|
367
290
|
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
},
|
|
374
|
-
});
|
|
291
|
+
```ts
|
|
292
|
+
const defaultOptions: Required<OverridableOptions> = {
|
|
293
|
+
ttl: fourteenDaysInSeconds,
|
|
294
|
+
cookieOptions: { httpOnly: true, secure: true, sameSite: 'lax', path: '/' },
|
|
295
|
+
}
|
|
375
296
|
```
|
|
376
297
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
- The password used to encrypt session data (to `seal`) is always the highest number found in the map (2 in the example).
|
|
380
|
-
- The passwords used to decrypt session data are all passwords in the map (this is how rotation works).
|
|
381
|
-
- Even if you do not provide a list at first, you can always move to multiple passwords afterwards. The first password you've used has a default id of 1.
|
|
298
|
+
### User Session Options
|
|
382
299
|
|
|
383
|
-
|
|
300
|
+
You may apply options during the Iron Session object initialization. These options will superseded and override any options set in Default Options. For example: refer to `cookieOptions` in `lib/session.ts` in the below [NextJS Example](#nextjs-example).
|
|
384
301
|
|
|
385
|
-
|
|
302
|
+
### Override Options
|
|
386
303
|
|
|
387
|
-
|
|
304
|
+
You may apply options during the `.save()` or `.destroy()` function calls. These options will superseded and override any options set in Default Options and User Session Options.
|
|
388
305
|
|
|
389
|
-
|
|
306
|
+
For example:
|
|
390
307
|
|
|
391
308
|
```ts
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
export default async function sendEmailRoute(req, res) {
|
|
397
|
-
const user = getUserFromDatabase(req.query.userId);
|
|
398
|
-
|
|
399
|
-
const seal = await sealData(
|
|
400
|
-
{
|
|
401
|
-
userId: user.id,
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
password: "complex_password_at_least_32_characters_long",
|
|
405
|
-
},
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
await sendEmail(
|
|
409
|
-
user.email,
|
|
410
|
-
"Magic link",
|
|
411
|
-
`Hey there ${user.name}, <a href="https://myapp.com/api/magicLogin?seal=${seal}">click here to login</a>.`,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
res.send({ ok: true });
|
|
309
|
+
type OverridableOptions = {
|
|
310
|
+
ttl?: number;
|
|
311
|
+
cookieOptions?: CookieOptions;
|
|
415
312
|
}
|
|
416
313
|
```
|
|
417
314
|
|
|
418
|
-
The default `ttl` for such seals is 14 days. To specify a `ttl`, provide it in seconds like so:
|
|
419
|
-
|
|
420
315
|
```ts
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const seal = await sealData(
|
|
424
|
-
{
|
|
425
|
-
userId: user.id,
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
password: "complex_password_at_least_32_characters_long",
|
|
429
|
-
ttl: fifteenMinutesInSeconds,
|
|
430
|
-
},
|
|
431
|
-
);
|
|
316
|
+
await session.save({ cookieOptions: { priority: 'high'} })
|
|
432
317
|
```
|
|
433
318
|
|
|
434
|
-
|
|
319
|
+
## NextJS Example
|
|
320
|
+
|
|
321
|
+
#### lib/session.ts
|
|
435
322
|
|
|
436
323
|
```ts
|
|
437
|
-
|
|
324
|
+
import {
|
|
325
|
+
IronSessionOptions, getIronSession, IronSessionData, getServerActionIronSession
|
|
326
|
+
} from 'iron-session'
|
|
438
327
|
|
|
439
|
-
import {
|
|
440
|
-
import { withIronSessionApiRoute } from "iron-session/next";
|
|
328
|
+
import { cookies } from 'next/headers';
|
|
441
329
|
|
|
442
|
-
export
|
|
443
|
-
|
|
444
|
-
|
|
330
|
+
export const sessionOptions: IronSessionOptions = {
|
|
331
|
+
password: 'change-this-this-is-not-a-secure-password',
|
|
332
|
+
cookieName: 'cookieNameInBrowser',
|
|
445
333
|
cookieOptions: {
|
|
446
|
-
secure: process.env.NODE_ENV ===
|
|
334
|
+
secure: process.env.NODE_ENV === 'production',
|
|
447
335
|
},
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
async function magicLoginRoute(req, res) {
|
|
451
|
-
const { userId } = await unsealData(req.query.seal, {
|
|
452
|
-
password: "complex_password_at_least_32_characters_long",
|
|
453
|
-
});
|
|
336
|
+
}
|
|
454
337
|
|
|
455
|
-
|
|
338
|
+
declare module 'iron-session' {
|
|
339
|
+
interface IronSessionData {
|
|
340
|
+
cookieVariable?: string;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
456
343
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
344
|
+
const getSession = async (req: Request, res: Response) => {
|
|
345
|
+
const session = getIronSession<IronSessionData>(req, res, sessionOptions)
|
|
346
|
+
return session
|
|
347
|
+
}
|
|
460
348
|
|
|
461
|
-
|
|
349
|
+
const getServerActionSession = async () => {
|
|
350
|
+
const session = getServerActionIronSession<IronSessionData>(sessionOptions, cookies())
|
|
351
|
+
return session
|
|
352
|
+
}
|
|
462
353
|
|
|
463
|
-
|
|
354
|
+
export {
|
|
355
|
+
getSession,
|
|
356
|
+
getServerActionSession
|
|
464
357
|
}
|
|
465
358
|
```
|
|
466
359
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
### Impersonation, login as someone else
|
|
470
|
-
|
|
471
|
-
You may want to impersonate your own users, to check how they see your application. This can be extremely useful. For example you could have a page that list all your users and with links you can click to impersonate them.
|
|
360
|
+
### getIronSession
|
|
472
361
|
|
|
473
|
-
|
|
362
|
+
#### src/app/clientActions.ts
|
|
474
363
|
|
|
475
364
|
```ts
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (!req.session.isAdmin) {
|
|
490
|
-
// let's pretend this route does not exists if user is not an admin
|
|
491
|
-
return res.status(404).end();
|
|
492
|
-
}
|
|
365
|
+
'use client'
|
|
366
|
+
|
|
367
|
+
export const submitCookieToStorageRouteHandler = async (cookie: string) => {
|
|
368
|
+
await fetch('http://localhost:3000/api/submitIronSessionCookie', {
|
|
369
|
+
method: 'POST',
|
|
370
|
+
body: JSON.stringify({
|
|
371
|
+
cookie,
|
|
372
|
+
}),
|
|
373
|
+
headers: {
|
|
374
|
+
'Content-Type': 'application/json',
|
|
375
|
+
},
|
|
376
|
+
})
|
|
377
|
+
}
|
|
493
378
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
379
|
+
export const readCookieFromStorageRouteHandler = async (): Promise<string> => {
|
|
380
|
+
const responseWithCookieFromStorage = await fetch('http://localhost:3000/api/readIronSessionCookie', {
|
|
381
|
+
method: 'GET',
|
|
382
|
+
headers: {
|
|
383
|
+
'Content-Type': 'application/json',
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
const data = await responseWithCookieFromStorage.json();
|
|
387
|
+
const cookieValue = data?.cookieInStorage || 'No Cookie In Storage'
|
|
388
|
+
return cookieValue
|
|
500
389
|
}
|
|
501
390
|
```
|
|
502
391
|
|
|
503
|
-
|
|
392
|
+
#### src/api/submitIronSessionCookie/route.ts
|
|
504
393
|
|
|
505
394
|
```ts
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// let's pretend this route does not exists if user is not an admin
|
|
521
|
-
return res.status(404).end();
|
|
395
|
+
import { getSession } from '../../../../lib/session'
|
|
396
|
+
|
|
397
|
+
export async function POST(request: Request) {
|
|
398
|
+
try {
|
|
399
|
+
const requestBody = await request.json()
|
|
400
|
+
const { cookie }: { cookie: string } = requestBody
|
|
401
|
+
const response = new Response()
|
|
402
|
+
const session = await getSession(request, response)
|
|
403
|
+
session.cookieVariable = cookie
|
|
404
|
+
await session.save()
|
|
405
|
+
return response
|
|
406
|
+
} catch (error: unknown) {
|
|
407
|
+
console.error((error as Error).message)
|
|
408
|
+
return new Response(JSON.stringify({ message: (error as Error).message }), { status: 500 })
|
|
522
409
|
}
|
|
523
|
-
|
|
524
|
-
req.session.user = req.session.originalUser;
|
|
525
|
-
delete req.session.originalUser;
|
|
526
|
-
await req.session.save();
|
|
527
|
-
res.redirect("/dashboard");
|
|
528
410
|
}
|
|
529
411
|
```
|
|
530
412
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
If you want cookies to expire when the user closes the browser, pass `maxAge: undefined` in cookie options, this way:
|
|
413
|
+
#### src/api/readIronSessionCookie/route.ts
|
|
534
414
|
|
|
535
415
|
```ts
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
{
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
maxAge: undefined,
|
|
549
|
-
secure: process.env.NODE_ENV === "production",
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
);
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
Beware, modern browsers might not delete cookies at all using this technique because of [session restoring](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie).
|
|
556
|
-
|
|
557
|
-
### Firebase usage
|
|
558
|
-
|
|
559
|
-
This library can be used with Firebase, as long as you set the cookie name to `__session` which seems to be the only valid cookie name there.
|
|
560
|
-
|
|
561
|
-
## API
|
|
562
|
-
|
|
563
|
-
### ironOptions
|
|
564
|
-
|
|
565
|
-
Only two options are required: `password` and `cookieName`. Everything else is automatically computed and usually doesn't need to be changed.
|
|
566
|
-
|
|
567
|
-
- `password`, **required**: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use https://1password.com/password-generator/ to generate strong passwords. `password` can be either a `string` or an `array` of objects like this: `[{id: 2, password: "..."}, {id: 1, password: "..."}]` to allow for password rotation.
|
|
568
|
-
- `cookieName`, **required**: Name of the cookie to be stored
|
|
569
|
-
- `ttl`, _optional_: In seconds. Default to the equivalent of 14 days. You can set this to `0` and iron-session will compute the maximum allowed value by cookies (~70 years).
|
|
570
|
-
- [`cookieOptions`](https://github.com/jshttp/cookie#cookieserializename-value-options), _optional_: Any option available from [jshttp/cookie#serialize](https://github.com/jshttp/cookie#cookieserializename-value-options). Default to:
|
|
571
|
-
|
|
572
|
-
```js
|
|
573
|
-
{
|
|
574
|
-
httpOnly: true,
|
|
575
|
-
secure: true, // true when using https, false otherwise
|
|
576
|
-
sameSite: "lax", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax
|
|
577
|
-
// The next line makes sure browser will expire cookies before seals are considered expired by the server. It also allows for clock difference of 60 seconds maximum between servers and clients.
|
|
578
|
-
maxAge: (ttl === 0 ? 2147483647 : ttl) - 60,
|
|
579
|
-
path: "/",
|
|
580
|
-
// other options:
|
|
581
|
-
// domain, if you want the cookie to be valid for the whole domain and subdomains, use domain: example.com
|
|
582
|
-
// encode, there should be no need to use this option, encoding is done by iron-session already
|
|
583
|
-
// expires, there should be no need to use this option, maxAge takes precedence
|
|
416
|
+
import { NextResponse } from 'next/server'
|
|
417
|
+
import { getSession } from '../../../../lib/session'
|
|
418
|
+
|
|
419
|
+
export async function GET(request: Request, response: Response) {
|
|
420
|
+
try {
|
|
421
|
+
const session = await getSession(request, response)
|
|
422
|
+
const cookeValue = session.cookieVariable || 'No Cookie Stored!'
|
|
423
|
+
return NextResponse.json({ cookieInStorage: cookeValue })
|
|
424
|
+
} catch (error: unknown) {
|
|
425
|
+
console.error((error as Error).message)
|
|
426
|
+
return new Response(JSON.stringify({ message: (error as Error).message }), { status: 500 })
|
|
427
|
+
}
|
|
584
428
|
}
|
|
585
429
|
```
|
|
586
430
|
|
|
587
|
-
###
|
|
431
|
+
### getServerActionIronSession
|
|
588
432
|
|
|
589
|
-
|
|
433
|
+
#### src/app/serverActions.ts
|
|
590
434
|
|
|
591
435
|
```ts
|
|
592
|
-
|
|
436
|
+
'use server'
|
|
593
437
|
|
|
594
|
-
|
|
595
|
-
function userRoute(req, res) {
|
|
596
|
-
res.send({ user: req.session.user });
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
cookieName: "myapp_cookiename",
|
|
600
|
-
password: "complex_password_at_least_32_characters_long",
|
|
601
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
602
|
-
cookieOptions: {
|
|
603
|
-
secure: process.env.NODE_ENV === "production",
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
);
|
|
438
|
+
import { getServerActionSession } from '../../lib/session'
|
|
607
439
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
(req, res) => {
|
|
614
|
-
// Infer max cookie from request
|
|
615
|
-
const maxCookieAge = getMaxCookieAge(req);
|
|
616
|
-
return {
|
|
617
|
-
cookieName: "myapp_cookiename",
|
|
618
|
-
password: "complex_password_at_least_32_characters_long",
|
|
619
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
620
|
-
cookieOptions: {
|
|
621
|
-
// setMaxCookie age here.
|
|
622
|
-
maxCookieAge,
|
|
623
|
-
secure: process.env.NODE_ENV === "production",
|
|
624
|
-
},
|
|
625
|
-
};
|
|
626
|
-
},
|
|
627
|
-
);
|
|
628
|
-
```
|
|
440
|
+
export const submitCookieToStorageServerAction = async (cookie: string) => {
|
|
441
|
+
const session = await getServerActionSession()
|
|
442
|
+
session.cookieVariable = cookie
|
|
443
|
+
await session.save()
|
|
444
|
+
}
|
|
629
445
|
|
|
630
|
-
|
|
446
|
+
export const readCookieFromStorageServerAction = async (): Promise<string> => {
|
|
447
|
+
const session = await getServerActionSession()
|
|
448
|
+
return session.cookieVariable || 'No Cookie Stored!'
|
|
449
|
+
}
|
|
450
|
+
```
|
|
631
451
|
|
|
632
|
-
|
|
452
|
+
#### next.config.js
|
|
633
453
|
|
|
634
454
|
```ts
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
return {
|
|
640
|
-
props: {
|
|
641
|
-
user: req.session.user,
|
|
642
|
-
},
|
|
643
|
-
};
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
cookieName: "myapp_cookiename",
|
|
647
|
-
password: "complex_password_at_least_32_characters_long",
|
|
648
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
649
|
-
cookieOptions: {
|
|
650
|
-
secure: process.env.NODE_ENV === "production",
|
|
455
|
+
/** @type {import('next').NextConfig} */
|
|
456
|
+
const nextConfig = {
|
|
457
|
+
experimental: {
|
|
458
|
+
serverActions: true,
|
|
651
459
|
},
|
|
652
|
-
}
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
// You can also pass an async or sync function which takes request and response object and return IronSessionOptions
|
|
656
|
-
export const getServerSideProps = withIronSessionSsr(
|
|
657
|
-
async function getServerSideProps({ req }) {
|
|
658
|
-
return {
|
|
659
|
-
props: {
|
|
660
|
-
user: req.session.user,
|
|
661
|
-
},
|
|
662
|
-
};
|
|
663
|
-
},
|
|
664
|
-
(req, res) => {
|
|
665
|
-
return {
|
|
666
|
-
cookieName: "myapp_cookiename",
|
|
667
|
-
password: "complex_password_at_least_32_characters_long",
|
|
668
|
-
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
|
669
|
-
cookieOptions: {
|
|
670
|
-
secure: process.env.NODE_ENV === "production",
|
|
671
|
-
},
|
|
672
|
-
};
|
|
673
|
-
},
|
|
674
|
-
);
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
### Express: ironSession(ironOptions)
|
|
678
|
-
|
|
679
|
-
Creates an express middleware that adds a `session` object to the request.
|
|
680
|
-
|
|
681
|
-
```js
|
|
682
|
-
import { ironSession } from "iron-session/express";
|
|
683
|
-
|
|
684
|
-
app.use(ironSession(ironOptions));
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### session.save()
|
|
688
|
-
|
|
689
|
-
Saves the session and sets the cookie header to be sent once the response is sent.
|
|
460
|
+
}
|
|
690
461
|
|
|
691
|
-
|
|
692
|
-
await req.session.save();
|
|
462
|
+
module.exports = nextConfig
|
|
693
463
|
```
|
|
694
464
|
|
|
695
|
-
### session.destroy()
|
|
696
|
-
|
|
697
|
-
Empties the session object and sets the cookie header to be sent once the response is sent. The browser will then remove the cookie automatically.
|
|
698
|
-
|
|
699
|
-
You don't have to call `req.session.save()` after calling `req.session.destroy()`. The session is saved automatically.
|
|
700
|
-
|
|
701
465
|
## FAQ
|
|
702
466
|
|
|
703
|
-
###
|
|
704
|
-
|
|
705
|
-
This makes your sessions stateless: you do not have to store session data on your server. You do not need another server or service to store session data. This is particularly useful in serverless architectures where you're trying to reduce your backend dependencies.
|
|
706
|
-
|
|
707
|
-
### What are the drawbacks?
|
|
708
|
-
|
|
709
|
-
There are some drawbacks to this approach:
|
|
710
|
-
|
|
711
|
-
- you cannot invalidate a seal when needed because there's no state stored on the server-side about them. We consider that the way the cookie is stored reduces the possibility for this eventuality to happen. Also, in most applications the first thing you do when receiving an authenticated request is to validate the user and their rights in your database, which defeats the case where someone would try to use a token while their account was deactivated/deleted. Now if someone steals a user token you should have a process in place to mitigate that: deactivate the user and force a re-login with a flag in your database for example.
|
|
712
|
-
- application not supporting cookies won't work, but you can use [iron-store](https://github.com/vvo/iron-store/) to implement something similar. In the future, we could allow `iron-session` to accept [basic auth](https://tools.ietf.org/html/rfc7617) or bearer token methods too. Open an issue if you're interested.
|
|
713
|
-
- on most browsers, you're limited to 4,096 bytes per cookie. To give you an idea, an `iron-session` cookie containing `{user: {id: 100}}` is 265 bytes signed and encrypted: still plenty of available cookie space in here.
|
|
714
|
-
- performance: crypto on the server-side could be slow, if that's the case let me know. Also, cookies are sent to every request to your website, even images, so this could be an issue
|
|
467
|
+
### When should I use getIronSession or getServerActionIronSession?
|
|
715
468
|
|
|
716
|
-
|
|
717
|
-
More information can also be found on the [Ruby On Rails website](https://guides.rubyonrails.org/security.html#session-storage) which uses the same technique.
|
|
469
|
+
Use `getIronSession` when you wish to use Iron Session in a client-side environment with API Route Handlers and use `getServerActionIronSession` when you wish to use Iron Session in a server-side environment with Server Components.
|
|
718
470
|
|
|
719
|
-
|
|
471
|
+
For NextJS projects using App Router with Server Actions enabled in their `next.config.js` file, using `getServerActionIronSession` is preferable for two reasons:
|
|
720
472
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
- JWT is a standard, it stores metadata in the JWT token themselves to ensure communication between different systems is flawless.
|
|
724
|
-
- JWT tokens are not encrypted, the payload is visible by customers if they manage to inspect the seal. You would have to use [JWE](https://tools.ietf.org/html/rfc7516) to achieve the same.
|
|
725
|
-
- @hapi/iron mechanism is not a standard, it's a way to sign and encrypt data into seals
|
|
726
|
-
|
|
727
|
-
Depending on your own needs and preferences, `iron-session` may or may not fit you.
|
|
728
|
-
|
|
729
|
-
## Project status
|
|
730
|
-
|
|
731
|
-
✅ Production ready and maintained.
|
|
473
|
+
- allows Iron Session to be called and used from a server-side environment
|
|
474
|
+
- allows for more concise code. Server Actions can be called directly from your components without the need for a manually created API route. You can see the smaller amount of code used for `getServerActionIronSession` compared to `getIronSession` in the example.
|
|
732
475
|
|
|
733
476
|
## Credits
|
|
734
477
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
- https://
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
<
|
|
752
|
-
|
|
753
|
-
<td align="center"><a href="http://www.afterecon.com/"><img src="https://avatars.githubusercontent.com/u/5559355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>John Vandivier</b></sub></a><br /><a href="https://github.com/vvo/iron-session/commits?author=Vandivier" title="Code">💻</a> <a href="#ideas-Vandivier" title="Ideas, Planning, & Feedback">🤔</a> <a href="#example-Vandivier" title="Examples">💡</a></td>
|
|
754
|
-
<td align="center"><a href="https://searchableguy.com"><img src="https://avatars.githubusercontent.com/u/73341821?v=4?s=100" width="100px;" alt=""/><br /><sub><b>searchableguy</b></sub></a><br /><a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Tests">⚠️</a> <a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Documentation">📖</a> <a href="https://github.com/vvo/iron-session/commits?author=searchableguy" title="Code">💻</a></td>
|
|
755
|
-
</tr>
|
|
756
|
-
</table>
|
|
757
|
-
|
|
758
|
-
<!-- markdownlint-restore -->
|
|
759
|
-
<!-- prettier-ignore-end -->
|
|
760
|
-
|
|
761
|
-
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
762
|
-
|
|
763
|
-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
478
|
+
- [Eran Hammer and hapi.js contributors](https://github.com/hapijs/iron/graphs/contributors)
|
|
479
|
+
for creating the underlying cryptography library
|
|
480
|
+
[`@hapi/iron`](https://hapi.dev/module/iron/).
|
|
481
|
+
- [Divyansh Singh](https://github.com/brc-dd) for reimplementing `@hapi/iron` as
|
|
482
|
+
[`iron-webcrypto`](https://github.com/brc-dd/iron-webcrypto) using standard
|
|
483
|
+
web APIs.
|
|
484
|
+
- [Hoang Vo](https://github.com/hoangvvo) for advice and guidance while building
|
|
485
|
+
this module. Hoang built
|
|
486
|
+
[`next-connect`](https://github.com/hoangvvo/next-connect) and
|
|
487
|
+
[`next-session`](https://github.com/hoangvvo/next-session).
|
|
488
|
+
- All the
|
|
489
|
+
[contributors](https://github.com/vvo/iron-session/graphs/contributors) for
|
|
490
|
+
making this project better.
|
|
491
|
+
|
|
492
|
+
## Good Reads
|
|
493
|
+
|
|
494
|
+
- <https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html>
|
|
495
|
+
- <https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html>
|