Crate typed_session

source ·
Expand description

Async strongly-typed HTTP sessions.

This crate provides a session handling mechanism with abstract session store (typically a database). The crate is not meant to be used directly, but should be wrapped into middleware for your web-framework of choice.

This crate handles all the typical plumbing associated with session handling, including:

  • change tracking,
  • expiry and automatic renewal and
  • generation of session ids.

On the “front-end” of this crate, the SessionStore provides a simple interface to load and store sessions given an identifying string, typically the value of a cookie. The Session type has a type parameter SessionData that decides what session-specific data is stored in the database. The user on the front-end is responsible for communicating session cookies to the client by performing the SessionCookieCommand returned by SessionStore::store_session.

On the “back-end” of this crate, the trait SessionStoreConnector expects a simple CRUD-based interface for handling sessions in a database.

Change tracking

Changes are tracked automatically in an efficient way. If a client has no session or an invalid session, a new session is created for that client. However, only if the session contains meaningful data it is stored and communicated to the client. Session data is assumed to be meaningful when it has been accessed mutably or the session was explicitly constructed with non-default data. Mutably accessing or mutating the expiry is not considered enough for the session to actually be stored.

Once the session is stored, if either the data or expiry is accessed mutably by a future request, it is updated. Each update generates a new session id to prevent simultaneous updates of the same session from producing unexpected results. If the session is not updated, then we neither touch the session store, nor do we communicate any session cookie to the client.

Session expiry

Session expiry is only checked when the session is loaded from the store. If it is expired, the store ignores it and returns no session. The session’s expiry can be updated manually, or automatically with a SessionRenewalStrategy. In case the session is renewed automatically, the session may be updated by the session store, even if neither its data nor expiry was accessed mutably.

Note that expired sessions are not deleted from the session store. This is left to a background job that needs to be set up independently of this crate. Also, expired cookies are not deleted, it is left to the browser to take care of that.

Manual session removal

While expiry does not actually delete sessions, but leaves this up to external jobs, sessions can be deleted from both the session store and the client using the Session::delete function. This marks the session for deletion, such that it is deleted from the store when SessionStore::store_session is called. The return value of store_session is then SessionCookieCommand::Delete, indicating to the web framework to set the Set-Cookie header such that the cookie is deleted.

Security

Sessions are identified by an alphanumeric string including upper and lower case letters from the English alphabet. This gives log_2(26+26+10) ≥ 5.95 bits of entropy per character. The default cookie length is 32, resulting in log_2(26+26+10) * 32 ≥ 190 bits of entropy. The OWASP® Foundation recommends session ids to have at least 128 bits of entropy. That is, 64 bits of actual entropy, where a good PRNG is assumed to produce 0.5 bits of entropy per bit. The random source used by default is rand::rngs::ThreadRng, from the rand crate, which is secure.

Session data is stored only in the session store along with a hashed session id, while the client only stores the unhashed session id.

Note that the OWASP® Foundation does not require session ids to be hashed. We anyways use the fast and secure hash function provided by crate blake3 for additional security in case the session store gets compromised.

This crate updates the session id whenever the session data has changed or the session is expired. The session id update must be supported by the session store backend in a way that does not allow session branching, i.e. the creation of two different sessions through the simultaneous update of a single session. See SessionStoreConnector::update_session for more details.

Example

use typed_session::{Session, SessionStore, MemoryStore};

use rand::thread_rng;
// Initialise a new database connection.
// This is used by the session store to load and store sessions.
// In this example, we use the debug session store `MemoryStore` directly as a database connection,
// but usually you would pass some type wrapping e.g. a connection to Postgres or Redis.
let mut connection = MemoryStore::new();

// Init a new session store we can persist sessions to.
let mut store = SessionStore::new(SessionRenewalStrategy::Ignore);

// Create and store a new session.
// The session can hold arbitrary data, but session stores are type safe,
// i.e. all sessions must hold data of the same type.
// Use e.g. an enum to distinguish session states like "anonymous" or "logged-in as user X".
let session = Session::new_with_data(15);
let SessionCookieCommand::Set { cookie_value, .. } = store.store_session(session, &mut connection)
    .await? else { unreachable!("New sessions without expiry always set the cookie") };
// The set_cookie_command contains the cookie value and the expiry to be sent to the client.

// Retrieve the session using the cookie.
let session = store.load_session(cookie_value, &mut connection).await?.unwrap();
assert_eq!(*session.data(), 15);

Debugging

To aid in debugging, this crate offers a debug backend implementation called [MemoryStore] under the feature flag memory-store.

Comparison with crate async-session

This crate was designed after async-session. The main difference is that the session data is generic, such that the user can plug in any type they want. If needed, the same HashMap<String, String> can be used as session data type.

Structs

  • A debug cookie generator that generates an ascending sequence of integers, formatted as strings padded with zeroes.
  • The default cookie generator with focus on security. It uses ThreadRng as a random source and the Alphanumeric distribution to generate cookie strings. This gives log_2(26+26+10) ≥ 5.95 bits of entropy per character.
  • A session with a client. This type handles the creation, updating and deletion of sessions. It is marked #[must_use], as dropping it will not update the session store. Instead, it should be passed to SessionStore::store_session.
  • A session id.
  • An async session store.

Enums

  • All errors that can occur in this crate.
  • Indicates if the client’s session cookie should be updated. Annotated with #[must_use], because silently dropping this very likely indicates that the communication of the session to the client was forgotten about.
  • The expiry of a session. Either a given date and time, or never.
  • The strategy to renew sessions.
  • The result of writing a session, indicating if the session could be written, or if the id collided. Annotated with #[must_use], because silently dropping this may cause sessions to be dropped silently.

Traits

  • A type with the ability to generate cookies. If the generator needs some mutable state, then it has to be behind a mutex or similar. This is to allow handling concurrent requests with a single session store instance.
  • This is the backend-facing interface of the session store. It defines simple CRUD-methods on sessions.

Type Aliases