actix_session/config.rs
1//! Configuration options to tune the behaviour of [`SessionMiddleware`].
2
3use actix_web::cookie::{time::Duration, Key, SameSite};
4use derive_more::derive::From;
5
6use crate::{storage::SessionStore, SessionMiddleware};
7
8/// Determines what type of session cookie should be used and how its lifecycle should be managed.
9///
10/// Used by [`SessionMiddlewareBuilder::session_lifecycle`].
11#[derive(Debug, Clone, From)]
12#[non_exhaustive]
13pub enum SessionLifecycle {
14 /// The session cookie will expire when the current browser session ends.
15 ///
16 /// When does a browser session end? It depends on the browser! Chrome, for example, will often
17 /// continue running in the background when the browser is closed—session cookies are not
18 /// deleted and they will still be available when the browser is opened again.
19 /// Check the documentation of the browsers you are targeting for up-to-date information.
20 BrowserSession(BrowserSession),
21
22 /// The session cookie will be a [persistent cookie].
23 ///
24 /// Persistent cookies have a pre-determined lifetime, specified via the `Max-Age` or `Expires`
25 /// attribute. They do not disappear when the current browser session ends.
26 ///
27 /// [persistent cookie]: https://www.whitehatsec.com/glossary/content/persistent-session-cookie
28 PersistentSession(PersistentSession),
29}
30
31/// A [session lifecycle](SessionLifecycle) strategy where the session cookie expires when the
32/// browser's current session ends.
33///
34/// When does a browser session end? It depends on the browser. Chrome, for example, will often
35/// continue running in the background when the browser is closed—session cookies are not deleted
36/// and they will still be available when the browser is opened again. Check the documentation of
37/// the browsers you are targeting for up-to-date information.
38///
39/// Due to its `Into<SessionLifecycle>` implementation, a `BrowserSession` can be passed directly
40/// to [`SessionMiddlewareBuilder::session_lifecycle()`].
41#[derive(Debug, Clone)]
42pub struct BrowserSession {
43 state_ttl: Duration,
44 state_ttl_extension_policy: TtlExtensionPolicy,
45}
46
47impl BrowserSession {
48 /// Sets a time-to-live (TTL) when storing the session state in the storage backend.
49 ///
50 /// We do not want to store session states indefinitely, otherwise we will inevitably run out of
51 /// storage by holding on to the state of countless abandoned or expired sessions!
52 ///
53 /// We are dealing with the lifecycle of two uncorrelated object here: the session cookie
54 /// and the session state. It is not a big issue if the session state outlives the cookie—
55 /// we are wasting some space in the backend storage, but it will be cleaned up eventually.
56 /// What happens, instead, if the cookie outlives the session state? A new session starts—
57 /// e.g. if sessions are being used for authentication, the user is de-facto logged out.
58 ///
59 /// It is not possible to predict with certainty how long a browser session is going to
60 /// last—you need to provide a reasonable upper bound. You do so via `state_ttl`—it dictates
61 /// what TTL should be used for session state when the lifecycle of the session cookie is
62 /// tied to the browser session length. [`SessionMiddleware`] will default to 1 day if
63 /// `state_ttl` is left unspecified.
64 ///
65 /// You can mitigate the risk of the session cookie outliving the session state by
66 /// specifying a more aggressive state TTL extension policy - check out
67 /// [`BrowserSession::state_ttl_extension_policy`] for more details.
68 pub fn state_ttl(mut self, ttl: Duration) -> Self {
69 self.state_ttl = ttl;
70 self
71 }
72
73 /// Determine under what circumstances the TTL of your session state should be extended.
74 ///
75 /// Defaults to [`TtlExtensionPolicy::OnStateChanges`] if left unspecified.
76 ///
77 /// See [`TtlExtensionPolicy`] for more details.
78 pub fn state_ttl_extension_policy(mut self, ttl_extension_policy: TtlExtensionPolicy) -> Self {
79 self.state_ttl_extension_policy = ttl_extension_policy;
80 self
81 }
82}
83
84impl Default for BrowserSession {
85 fn default() -> Self {
86 Self {
87 state_ttl: default_ttl(),
88 state_ttl_extension_policy: default_ttl_extension_policy(),
89 }
90 }
91}
92
93/// A [session lifecycle](SessionLifecycle) strategy where the session cookie will be [persistent].
94///
95/// Persistent cookies have a pre-determined expiration, specified via the `Max-Age` or `Expires`
96/// attribute. They do not disappear when the current browser session ends.
97///
98/// Due to its `Into<SessionLifecycle>` implementation, a `PersistentSession` can be passed directly
99/// to [`SessionMiddlewareBuilder::session_lifecycle()`].
100///
101/// # Examples
102/// ```
103/// use actix_web::cookie::time::Duration;
104/// use actix_session::SessionMiddleware;
105/// use actix_session::config::{PersistentSession, TtlExtensionPolicy};
106///
107/// const SECS_IN_WEEK: i64 = 60 * 60 * 24 * 7;
108///
109/// // a session lifecycle with a time-to-live (expiry) of 1 week and default extension policy
110/// PersistentSession::default().session_ttl(Duration::seconds(SECS_IN_WEEK));
111///
112/// // a session lifecycle with the default time-to-live (expiry) and a custom extension policy
113/// PersistentSession::default()
114/// // this policy causes the session state's TTL to be refreshed on every request
115/// .session_ttl_extension_policy(TtlExtensionPolicy::OnEveryRequest);
116/// ```
117///
118/// [persistent]: https://www.whitehatsec.com/glossary/content/persistent-session-cookie
119#[derive(Debug, Clone)]
120pub struct PersistentSession {
121 session_ttl: Duration,
122 ttl_extension_policy: TtlExtensionPolicy,
123}
124
125impl PersistentSession {
126 /// Specifies how long the session cookie should live.
127 ///
128 /// The session TTL is also used as the TTL for the session state in the storage backend.
129 ///
130 /// Defaults to 1 day.
131 ///
132 /// A persistent session can live more than the specified TTL if the TTL is extended.
133 /// See [`session_ttl_extension_policy`](Self::session_ttl_extension_policy) for more details.
134 #[doc(alias = "max_age", alias = "max age", alias = "expires")]
135 pub fn session_ttl(mut self, session_ttl: Duration) -> Self {
136 self.session_ttl = session_ttl;
137 self
138 }
139
140 /// Determines under what circumstances the TTL of your session should be extended.
141 /// See [`TtlExtensionPolicy`] for more details.
142 ///
143 /// Defaults to [`TtlExtensionPolicy::OnStateChanges`].
144 pub fn session_ttl_extension_policy(
145 mut self,
146 ttl_extension_policy: TtlExtensionPolicy,
147 ) -> Self {
148 self.ttl_extension_policy = ttl_extension_policy;
149 self
150 }
151}
152
153impl Default for PersistentSession {
154 fn default() -> Self {
155 Self {
156 session_ttl: default_ttl(),
157 ttl_extension_policy: default_ttl_extension_policy(),
158 }
159 }
160}
161
162/// Configuration for which events should trigger an extension of the time-to-live for your session.
163///
164/// If you are using a [`BrowserSession`], `TtlExtensionPolicy` controls how often the TTL of the
165/// session state should be refreshed. The browser is in control of the lifecycle of the session
166/// cookie.
167///
168/// If you are using a [`PersistentSession`], `TtlExtensionPolicy` controls both the expiration of
169/// the session cookie and the TTL of the session state on the storage backend.
170#[derive(Debug, Clone)]
171#[non_exhaustive]
172pub enum TtlExtensionPolicy {
173 /// The TTL is refreshed every time the server receives a request associated with a session.
174 ///
175 /// # Performance impact
176 /// Refreshing the TTL on every request is not free. It implies a refresh of the TTL on the
177 /// session state. This translates into a request over the network if you are using a remote
178 /// system as storage backend (e.g. Redis). This impacts both the total load on your storage
179 /// backend (i.e. number of queries it has to handle) and the latency of the requests served by
180 /// your server.
181 OnEveryRequest,
182
183 /// The TTL is refreshed every time the session state changes or the session key is renewed.
184 OnStateChanges,
185}
186
187/// Determines how to secure the content of the session cookie.
188///
189/// Used by [`SessionMiddlewareBuilder::cookie_content_security`].
190#[derive(Debug, Clone, Copy)]
191pub enum CookieContentSecurity {
192 /// The cookie content is encrypted when using `CookieContentSecurity::Private`.
193 ///
194 /// Encryption guarantees confidentiality and integrity: the client cannot tamper with the
195 /// cookie content nor decode it, as long as the encryption key remains confidential.
196 Private,
197
198 /// The cookie content is signed when using `CookieContentSecurity::Signed`.
199 ///
200 /// Signing guarantees integrity, but it doesn't ensure confidentiality: the client cannot
201 /// tamper with the cookie content, but they can read it.
202 Signed,
203}
204
205pub(crate) const fn default_ttl() -> Duration {
206 Duration::days(1)
207}
208
209pub(crate) const fn default_ttl_extension_policy() -> TtlExtensionPolicy {
210 TtlExtensionPolicy::OnStateChanges
211}
212
213/// A fluent, customized [`SessionMiddleware`] builder.
214#[must_use]
215pub struct SessionMiddlewareBuilder<Store: SessionStore> {
216 storage_backend: Store,
217 configuration: Configuration,
218}
219
220impl<Store: SessionStore> SessionMiddlewareBuilder<Store> {
221 pub(crate) fn new(store: Store, configuration: Configuration) -> Self {
222 Self {
223 storage_backend: store,
224 configuration,
225 }
226 }
227
228 /// Set the name of the cookie used to store the session ID.
229 ///
230 /// Defaults to `id`.
231 pub fn cookie_name(mut self, name: String) -> Self {
232 self.configuration.cookie.name = name;
233 self
234 }
235
236 /// Set the `Secure` attribute for the cookie used to store the session ID.
237 ///
238 /// If the cookie is set as secure, it will only be transmitted when the connection is secure
239 /// (using `https`).
240 ///
241 /// Default is `true`.
242 pub fn cookie_secure(mut self, secure: bool) -> Self {
243 self.configuration.cookie.secure = secure;
244 self
245 }
246
247 /// Determines what type of session cookie should be used and how its lifecycle should be managed.
248 /// Check out [`SessionLifecycle`]'s documentation for more details on the available options.
249 ///
250 /// Default is [`SessionLifecycle::BrowserSession`].
251 ///
252 /// # Examples
253 /// ```
254 /// use actix_web::cookie::{Key, time::Duration};
255 /// use actix_session::{SessionMiddleware, config::PersistentSession};
256 /// use actix_session::storage::CookieSessionStore;
257 ///
258 /// const SECS_IN_WEEK: i64 = 60 * 60 * 24 * 7;
259 ///
260 /// // creates a session middleware with a time-to-live (expiry) of 1 week
261 /// SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&[0; 64]))
262 /// .session_lifecycle(
263 /// PersistentSession::default().session_ttl(Duration::seconds(SECS_IN_WEEK))
264 /// )
265 /// .build();
266 /// ```
267 pub fn session_lifecycle<S: Into<SessionLifecycle>>(mut self, session_lifecycle: S) -> Self {
268 match session_lifecycle.into() {
269 SessionLifecycle::BrowserSession(BrowserSession {
270 state_ttl,
271 state_ttl_extension_policy,
272 }) => {
273 self.configuration.cookie.max_age = None;
274 self.configuration.session.state_ttl = state_ttl;
275 self.configuration.ttl_extension_policy = state_ttl_extension_policy;
276 }
277 SessionLifecycle::PersistentSession(PersistentSession {
278 session_ttl,
279 ttl_extension_policy,
280 }) => {
281 self.configuration.cookie.max_age = Some(session_ttl);
282 self.configuration.session.state_ttl = session_ttl;
283 self.configuration.ttl_extension_policy = ttl_extension_policy;
284 }
285 }
286
287 self
288 }
289
290 /// Set the `SameSite` attribute for the cookie used to store the session ID.
291 ///
292 /// By default, the attribute is set to `Lax`.
293 pub fn cookie_same_site(mut self, same_site: SameSite) -> Self {
294 self.configuration.cookie.same_site = same_site;
295 self
296 }
297
298 /// Set the `Path` attribute for the cookie used to store the session ID.
299 ///
300 /// By default, the attribute is set to `/`.
301 pub fn cookie_path(mut self, path: String) -> Self {
302 self.configuration.cookie.path = path;
303 self
304 }
305
306 /// Set the `Domain` attribute for the cookie used to store the session ID.
307 ///
308 /// Use `None` to leave the attribute unspecified. If unspecified, the attribute defaults
309 /// to the same host that set the cookie, excluding subdomains.
310 ///
311 /// By default, the attribute is left unspecified.
312 pub fn cookie_domain(mut self, domain: Option<String>) -> Self {
313 self.configuration.cookie.domain = domain;
314 self
315 }
316
317 /// Choose how the session cookie content should be secured.
318 ///
319 /// - [`CookieContentSecurity::Private`] selects encrypted cookie content.
320 /// - [`CookieContentSecurity::Signed`] selects signed cookie content.
321 ///
322 /// # Default
323 /// By default, the cookie content is encrypted. Encrypted was chosen instead of signed as
324 /// default because it reduces the chances of sensitive information being exposed in the session
325 /// key by accident, regardless of [`SessionStore`] implementation you chose to use.
326 ///
327 /// For example, if you are using cookie-based storage, you definitely want the cookie content
328 /// to be encrypted—the whole session state is embedded in the cookie! If you are using
329 /// Redis-based storage, signed is more than enough - the cookie content is just a unique
330 /// tamper-proof session key.
331 pub fn cookie_content_security(mut self, content_security: CookieContentSecurity) -> Self {
332 self.configuration.cookie.content_security = content_security;
333 self
334 }
335
336 /// Set the `HttpOnly` attribute for the cookie used to store the session ID.
337 ///
338 /// If the cookie is set as `HttpOnly`, it will not be visible to any JavaScript snippets
339 /// running in the browser.
340 ///
341 /// Default is `true`.
342 pub fn cookie_http_only(mut self, http_only: bool) -> Self {
343 self.configuration.cookie.http_only = http_only;
344 self
345 }
346
347 /// Finalise the builder and return a [`SessionMiddleware`] instance.
348 #[must_use]
349 pub fn build(self) -> SessionMiddleware<Store> {
350 SessionMiddleware::from_parts(self.storage_backend, self.configuration)
351 }
352}
353
354#[derive(Clone)]
355pub(crate) struct Configuration {
356 pub(crate) cookie: CookieConfiguration,
357 pub(crate) session: SessionConfiguration,
358 pub(crate) ttl_extension_policy: TtlExtensionPolicy,
359}
360
361#[derive(Clone)]
362pub(crate) struct SessionConfiguration {
363 pub(crate) state_ttl: Duration,
364}
365
366#[derive(Clone)]
367pub(crate) struct CookieConfiguration {
368 pub(crate) secure: bool,
369 pub(crate) http_only: bool,
370 pub(crate) name: String,
371 pub(crate) same_site: SameSite,
372 pub(crate) path: String,
373 pub(crate) domain: Option<String>,
374 pub(crate) max_age: Option<Duration>,
375 pub(crate) content_security: CookieContentSecurity,
376 pub(crate) key: Key,
377}
378
379pub(crate) fn default_configuration(key: Key) -> Configuration {
380 Configuration {
381 cookie: CookieConfiguration {
382 secure: true,
383 http_only: true,
384 name: "id".into(),
385 same_site: SameSite::Lax,
386 path: "/".into(),
387 domain: None,
388 max_age: None,
389 content_security: CookieContentSecurity::Private,
390 key,
391 },
392 session: SessionConfiguration {
393 state_ttl: default_ttl(),
394 },
395 ttl_extension_policy: default_ttl_extension_policy(),
396 }
397}