diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index 1cb320db4..49b82aa01 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -25,14 +25,14 @@ //! async fn main() -> std::io::Result<()> { //! HttpServer::new(|| { //! let cors = Cors::default() -//! .allowed_origin("https://www.rust-lang.org") -//! .allowed_origin_fn(|origin, _req_head| { -//! origin.as_bytes().ends_with(b".rust-lang.org") -//! }) -//! .allowed_methods(vec!["GET", "POST"]) -//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) -//! .allowed_header(http::header::CONTENT_TYPE) -//! .max_age(3600); +//! .allowed_origin("https://www.rust-lang.org") +//! .allowed_origin_fn(|origin, _req_head| { +//! origin.as_bytes().ends_with(b".rust-lang.org") +//! }) +//! .allowed_methods(vec!["GET", "POST"]) +//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) +//! .allowed_header(http::header::CONTENT_TYPE) +//! .max_age(3600); //! //! App::new() //! .wrap(cors) diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index cadff8f23..6a4234f0e 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -19,7 +19,7 @@ all-features = true [dependencies] actix-service = "2" -actix-session = "0.8" +actix-session = "0.9" actix-utils = "3" actix-web = { version = "4", default-features = false, features = ["cookies", "secure-cookies"] } @@ -31,7 +31,7 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] actix-http = "3" actix-web = { version = "4", default-features = false, features = ["macros", "cookies", "secure-cookies"] } -actix-session = { version = "0.8", features = ["redis-rs-session", "cookie-session"] } +actix-session = { version = "0.9", features = ["redis-rs-session", "cookie-session"] } env_logger = "0.10" reqwest = { version = "0.11", default-features = false, features = ["cookies", "json"] } diff --git a/actix-identity/README.md b/actix-identity/README.md index 4db84407a..f77ce7b0b 100644 --- a/actix-identity/README.md +++ b/actix-identity/README.md @@ -11,7 +11,94 @@ -## Documentation & community resources + -- [API Documentation](https://docs.rs/actix-identity) -- Minimum Supported Rust Version (MSRV): 1.57 +Identity management for Actix Web. + +`actix-identity` can be used to track identity of a user across multiple requests. It is built on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session). + +## Getting started + +To start using identity management in your Actix Web application you must register [`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`: + +```rust +use actix_web::{cookie::Key, App, HttpServer, HttpResponse}; +use actix_identity::IdentityMiddleware; +use actix_session::{storage::RedisSessionStore, SessionMiddleware}; + +#[actix_web::main] +async fn main() { + // When using `Key::generate()` it is important to initialize outside of the + // `HttpServer::new` closure. When deployed the secret key should be read from a + // configuration file or environment variables. + let secret_key = Key::generate(); + + let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379") + .await + .unwrap(); + + HttpServer::new(move || { + App::new() + // Install the identity framework first. + .wrap(IdentityMiddleware::default()) + // The identity system is built on top of sessions. You must install the session + // middleware to leverage `actix-identity`. The session middleware must be mounted + // AFTER the identity middleware: `actix-web` invokes middleware in the OPPOSITE + // order of registration when it receives an incoming request. + .wrap(SessionMiddleware::new( + redis_store.clone(), + secret_key.clone() + )) + // Your request handlers [...] + }) +} +``` + +User identities can be created, accessed and destroyed using the [`Identity`] extractor in your request handlers: + +```rust +use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage}; +use actix_identity::Identity; +use actix_session::storage::RedisSessionStore; + +#[get("/")] +async fn index(user: Option) -> impl Responder { + if let Some(user) = user { + format!("Welcome! {}", user.id().unwrap()) + } else { + "Welcome Anonymous!".to_owned() + } +} + +#[post("/login")] +async fn login(request: HttpRequest) -> impl Responder { + // Some kind of authentication should happen here + // e.g. password-based, biometric, etc. + // [...] + + // attach a verified user identity to the active session + Identity::login(&request.extensions(), "User1".into()).unwrap(); + + HttpResponse::Ok() +} + +#[post("/logout")] +async fn logout(user: Identity) -> impl Responder { + user.logout(); + HttpResponse::Ok() +} +``` + +## Advanced configuration + +By default, `actix-identity` does not automatically log out users. You can change this behaviour by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`]. + +In particular, you can automatically log out users who: + +- have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`]; +- logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]). + +[`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline +[`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline + + diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 4b4a04202..d05e846bd 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -1,90 +1,96 @@ -//! Identity management for Actix Web. -//! -//! `actix-identity` can be used to track identity of a user across multiple requests. It is built -//! on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session). -//! -//! # Getting started -//! To start using identity management in your Actix Web application you must register -//! [`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`: -//! -//! ```no_run -//! # use actix_web::web; -//! use actix_web::{cookie::Key, App, HttpServer, HttpResponse}; -//! use actix_identity::IdentityMiddleware; -//! use actix_session::{storage::RedisSessionStore, SessionMiddleware}; -//! -//! #[actix_web::main] -//! async fn main() { -//! let secret_key = Key::generate(); -//! let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379") -//! .await -//! .unwrap(); -//! -//! HttpServer::new(move || { -//! App::new() -//! // Install the identity framework first. -//! .wrap(IdentityMiddleware::default()) -//! // The identity system is built on top of sessions. You must install the session -//! // middleware to leverage `actix-identity`. The session middleware must be mounted -//! // AFTER the identity middleware: `actix-web` invokes middleware in the OPPOSITE -//! // order of registration when it receives an incoming request. -//! .wrap(SessionMiddleware::new( -//! redis_store.clone(), -//! secret_key.clone() -//! )) -//! // Your request handlers [...] -//! # .default_service(web::to(|| HttpResponse::Ok())) -//! }) -//! # ; -//! } -//! ``` -//! -//! User identities can be created, accessed and destroyed using the [`Identity`] extractor in your -//! request handlers: -//! -//! ```no_run -//! use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage}; -//! use actix_identity::Identity; -//! use actix_session::storage::RedisSessionStore; -//! -//! #[get("/")] -//! async fn index(user: Option) -> impl Responder { -//! if let Some(user) = user { -//! format!("Welcome! {}", user.id().unwrap()) -//! } else { -//! "Welcome Anonymous!".to_owned() -//! } -//! } -//! -//! #[post("/login")] -//! async fn login(request: HttpRequest) -> impl Responder { -//! // Some kind of authentication should happen here -//! // e.g. password-based, biometric, etc. -//! // [...] -//! -//! // attach a verified user identity to the active session -//! Identity::login(&request.extensions(), "User1".into()).unwrap(); -//! -//! HttpResponse::Ok() -//! } -//! -//! #[post("/logout")] -//! async fn logout(user: Identity) -> impl Responder { -//! user.logout(); -//! HttpResponse::Ok() -//! } -//! ``` -//! -//! # Advanced configuration -//! By default, `actix-identity` does not automatically log out users. You can change this behaviour -//! by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`]. -//! -//! In particular, you can automatically log out users who: -//! - have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`]; -//! - logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]). -//! -//! [`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline -//! [`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline +/*! +Identity management for Actix Web. + +`actix-identity` can be used to track identity of a user across multiple requests. It is built +on top of HTTP sessions, via [`actix-session`](https://docs.rs/actix-session). + +# Getting started +To start using identity management in your Actix Web application you must register +[`IdentityMiddleware`] and `SessionMiddleware` as middleware on your `App`: + +```no_run +# use actix_web::web; +use actix_web::{cookie::Key, App, HttpServer, HttpResponse}; +use actix_identity::IdentityMiddleware; +use actix_session::{storage::RedisSessionStore, SessionMiddleware}; + +#[actix_web::main] +async fn main() { + // When using `Key::generate()` it is important to initialize outside of the + // `HttpServer::new` closure. When deployed the secret key should be read from a + // configuration file or environment variables. + let secret_key = Key::generate(); + + let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379") + .await + .unwrap(); + + HttpServer::new(move || { + App::new() + // Install the identity framework first. + .wrap(IdentityMiddleware::default()) + // The identity system is built on top of sessions. You must install the session + // middleware to leverage `actix-identity`. The session middleware must be mounted + // AFTER the identity middleware: `actix-web` invokes middleware in the OPPOSITE + // order of registration when it receives an incoming request. + .wrap(SessionMiddleware::new( + redis_store.clone(), + secret_key.clone(), + )) + // Your request handlers [...] + # .default_service(web::to(|| HttpResponse::Ok())) + }) +# ; +} +``` + +User identities can be created, accessed and destroyed using the [`Identity`] extractor in your +request handlers: + +```no_run +use actix_web::{get, post, HttpResponse, Responder, HttpRequest, HttpMessage}; +use actix_identity::Identity; +use actix_session::storage::RedisSessionStore; + +#[get("/")] +async fn index(user: Option) -> impl Responder { + if let Some(user) = user { + format!("Welcome! {}", user.id().unwrap()) + } else { + "Welcome Anonymous!".to_owned() + } +} + +#[post("/login")] +async fn login(request: HttpRequest) -> impl Responder { + // Some kind of authentication should happen here + // e.g. password-based, biometric, etc. + // [...] + + // attach a verified user identity to the active session + Identity::login(&request.extensions(), "User1".into()).unwrap(); + + HttpResponse::Ok() +} + +#[post("/logout")] +async fn logout(user: Identity) -> impl Responder { + user.logout(); + HttpResponse::Ok() +} +``` + +# Advanced configuration +By default, `actix-identity` does not automatically log out users. You can change this behaviour +by customising the configuration for [`IdentityMiddleware`] via [`IdentityMiddleware::builder`]. + +In particular, you can automatically log out users who: +- have been inactive for a while (see [`IdentityMiddlewareBuilder::visit_deadline`]; +- logged in too long ago (see [`IdentityMiddlewareBuilder::login_deadline`]). + +[`IdentityMiddlewareBuilder::visit_deadline`]: config::IdentityMiddlewareBuilder::visit_deadline +[`IdentityMiddlewareBuilder::login_deadline`]: config::IdentityMiddlewareBuilder::login_deadline +*/ #![forbid(unsafe_code)] #![deny(rust_2018_idioms, nonstandard_style, missing_docs)] diff --git a/actix-limitation/Cargo.toml b/actix-limitation/Cargo.toml index 54dd12968..1ae6d85c9 100644 --- a/actix-limitation/Cargo.toml +++ b/actix-limitation/Cargo.toml @@ -32,7 +32,7 @@ redis = { version = "0.24", default-features = false, features = ["tokio-comp"] time = "0.3" # session -actix-session = { version = "0.8", optional = true } +actix-session = { version = "0.9", optional = true } [dev-dependencies] actix-web = "4" diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index 4692f7785..c8d412fc1 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased +## 0.9.0 + - Remove use of `async-trait` on `SessionStore` trait. - Minimum supported Rust version (MSRV) is now 1.75. diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 623e1053e..cf0b7b130 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-session" -version = "0.8.0" +version = "0.9.0" authors = [ "Nikolay Kim ", "Luca Palmieri ", diff --git a/actix-session/README.md b/actix-session/README.md index 9e14caef1..0ecf47343 100644 --- a/actix-session/README.md +++ b/actix-session/README.md @@ -5,14 +5,130 @@ [![crates.io](https://img.shields.io/crates/v/actix-session?label=latest)](https://crates.io/crates/actix-session) -[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.8.0)](https://docs.rs/actix-session/0.8.0) +[![Documentation](https://docs.rs/actix-session/badge.svg?version=0.9.0)](https://docs.rs/actix-session/0.9.0) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-session) -[![Dependency Status](https://deps.rs/crate/actix-session/0.8.0/status.svg)](https://deps.rs/crate/actix-session/0.8.0) +[![Dependency Status](https://deps.rs/crate/actix-session/0.9.0/status.svg)](https://deps.rs/crate/actix-session/0.9.0) -## Documentation & Resources + -- [API Documentation](https://docs.rs/actix-session) -- [Example Projects](https://github.com/actix/examples/tree/master/auth/cookie-session) -- Minimum Supported Rust Version (MSRV): 1.57 +Session management for Actix Web. + +The HTTP protocol, at a first glance, is stateless: the client sends a request, the server parses its content, performs some processing and returns a response. The outcome is only influenced by the provided inputs (i.e. the request content) and whatever state the server queries while performing its processing. + +Stateless systems are easier to reason about, but they are not quite as powerful as we need them to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for every single request**. That is, for example, how 'Basic' Authentication works. While it may work for a machine user (i.e. an API client), it is impractical for a person—you do not want a login prompt on every single page you navigate to! + +There is a solution - **sessions**. Using sessions the server can attach state to a set of requests coming from the same client. They are built on top of cookies - the server sets a cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the cookie and play it back to the server when sending new requests (using the `Cookie` header). + +We refer to the cookie used for sessions as a **session cookie**. Its content is called **session key** (or **session ID**), while the state attached to the session is referred to as **session state**. + +`actix-session` provides an easy-to-use framework to manage sessions in applications built on top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality provided by `actix-session`; it takes care of all the session cookie handling and instructs the **storage backend** to create/delete/update the session state based on the operations performed against the active [`Session`]. + +`actix-session` provides some built-in storage backends: ([`CookieSessionStore`], [`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend by implementing the [`SessionStore`] trait. + +Further reading on sessions: + +- [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265); +- [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html). + +## Getting started + +To start using sessions in your Actix Web application you must register [`SessionMiddleware`] as a middleware on your `App`: + +```rust +use actix_web::{web, App, HttpServer, HttpResponse, Error}; +use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore}; +use actix_web::cookie::Key; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // When using `Key::generate()` it is important to initialize outside of the + // `HttpServer::new` closure. When deployed the secret key should be read from a + // configuration file or environment variables. + let secret_key = Key::generate(); + + let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379") + .await + .unwrap(); + + HttpServer::new(move || + App::new() + // Add session management to your application using Redis for session state storage + .wrap( + SessionMiddleware::new( + redis_store.clone(), + secret_key.clone(), + ) + ) + .default_service(web::to(|| HttpResponse::Ok()))) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` + +The session state can be accessed and modified by your request handlers using the [`Session`] extractor. Note that this doesn't work in the stream of a streaming response. + +```rust +use actix_web::Error; +use actix_session::Session; + +fn index(session: Session) -> Result<&'static str, Error> { + // access the session state + if let Some(count) = session.get::("counter")? { + println!("SESSION value: {}", count); + // modify the session state + session.insert("counter", count + 1)?; + } else { + session.insert("counter", 1)?; + } + + Ok("Welcome!") +} +``` + +## Choosing A Backend + +By default, `actix-session` does not provide any storage backend to retrieve and save the state attached to your sessions. You can enable: + +- a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["cookie-session"] } + ``` + +- a Redis-based backend via [`actix-redis`](https://docs.rs/actix-redis), [`RedisActorSessionStore`], using the `redis-actor-session` feature flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-actor-session"] } + ``` + +- a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using the `redis-rs-session` feature flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-rs-session"] } + ``` + + Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured connection: + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] } + ``` + +You can implement your own session storage backend using the [`SessionStore`] trait. + +[`SessionStore`]: storage::SessionStore +[`CookieSessionStore`]: storage::CookieSessionStore +[`RedisSessionStore`]: storage::RedisSessionStore +[`RedisActorSessionStore`]: storage::RedisActorSessionStore + + diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index fbacce290..31665e524 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -1,137 +1,145 @@ -//! Session management for Actix Web. -//! -//! The HTTP protocol, at a first glance, is stateless: the client sends a request, the server -//! parses its content, performs some processing and returns a response. The outcome is only -//! influenced by the provided inputs (i.e. the request content) and whatever state the server -//! queries while performing its processing. -//! -//! Stateless systems are easier to reason about, but they are not quite as powerful as we need them -//! to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for -//! every single request**. That is, for example, how 'Basic' Authentication works. While it may -//! work for a machine user (i.e. an API client), it is impractical for a person—you do not want a -//! login prompt on every single page you navigate to! -//! -//! There is a solution - **sessions**. Using sessions the server can attach state to a set of -//! requests coming from the same client. They are built on top of cookies - the server sets a -//! cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the -//! cookie and play it back to the server when sending new requests (using the `Cookie` header). -//! -//! We refer to the cookie used for sessions as a **session cookie**. Its content is called -//! **session key** (or **session ID**), while the state attached to the session is referred to as -//! **session state**. -//! -//! `actix-session` provides an easy-to-use framework to manage sessions in applications built on -//! top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality -//! provided by `actix-session`; it takes care of all the session cookie handling and instructs the -//! **storage backend** to create/delete/update the session state based on the operations performed -//! against the active [`Session`]. -//! -//! `actix-session` provides some built-in storage backends: ([`CookieSessionStore`], -//! [`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend -//! by implementing the [`SessionStore`] trait. -//! -//! Further reading on sessions: -//! - [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265); -//! - [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html). -//! -//! # Getting started -//! To start using sessions in your Actix Web application you must register [`SessionMiddleware`] -//! as a middleware on your `App`: -//! -//! ```no_run -//! use actix_web::{web, App, HttpServer, HttpResponse, Error}; -//! use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore}; -//! use actix_web::cookie::Key; -//! -//! #[actix_web::main] -//! async fn main() -> std::io::Result<()> { -//! // The secret key would usually be read from a configuration file/environment variables. -//! let secret_key = Key::generate(); -//! let redis_connection_string = "127.0.0.1:6379"; -//! HttpServer::new(move || -//! App::new() -//! // Add session management to your application using Redis for session state storage -//! .wrap( -//! SessionMiddleware::new( -//! RedisActorSessionStore::new(redis_connection_string), -//! secret_key.clone() -//! ) -//! ) -//! .default_service(web::to(|| HttpResponse::Ok()))) -//! .bind(("127.0.0.1", 8080))? -//! .run() -//! .await -//! } -//! ``` -//! -//! The session state can be accessed and modified by your request handlers using the [`Session`] -//! extractor. Note that this doesn't work in the stream of a streaming response. -//! -//! ```no_run -//! use actix_web::Error; -//! use actix_session::Session; -//! -//! fn index(session: Session) -> Result<&'static str, Error> { -//! // access the session state -//! if let Some(count) = session.get::("counter")? { -//! println!("SESSION value: {}", count); -//! // modify the session state -//! session.insert("counter", count + 1)?; -//! } else { -//! session.insert("counter", 1)?; -//! } -//! -//! Ok("Welcome!") -//! } -//! ``` -//! -//! # Choosing A Backend -//! -//! By default, `actix-session` does not provide any storage backend to retrieve and save the state -//! attached to your sessions. You can enable: -//! -//! - a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature -//! flag. -//! -//! ```toml -//! [dependencies] -//! # ... -//! actix-session = { version = "...", features = ["cookie-session"] } -//! ``` -//! -//! - a Redis-based backend via [`actix-redis`](https://docs.rs/actix-redis), -//! [`RedisActorSessionStore`], using the `redis-actor-session` feature flag. -//! -//! ```toml -//! [dependencies] -//! # ... -//! actix-session = { version = "...", features = ["redis-actor-session"] } -//! ``` -//! -//! - a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using -//! the `redis-rs-session` feature flag. -//! -//! ```toml -//! [dependencies] -//! # ... -//! actix-session = { version = "...", features = ["redis-rs-session"] } -//! ``` -//! -//! Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured -//! connection: -//! -//! ```toml -//! [dependencies] -//! # ... -//! actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] } -//! ``` -//! -//! You can implement your own session storage backend using the [`SessionStore`] trait. -//! -//! [`SessionStore`]: storage::SessionStore -//! [`CookieSessionStore`]: storage::CookieSessionStore -//! [`RedisSessionStore`]: storage::RedisSessionStore -//! [`RedisActorSessionStore`]: storage::RedisActorSessionStore +/*! +Session management for Actix Web. + +The HTTP protocol, at a first glance, is stateless: the client sends a request, the server +parses its content, performs some processing and returns a response. The outcome is only +influenced by the provided inputs (i.e. the request content) and whatever state the server +queries while performing its processing. + +Stateless systems are easier to reason about, but they are not quite as powerful as we need them +to be - e.g. how do you authenticate a user? The user would be forced to authenticate **for +every single request**. That is, for example, how 'Basic' Authentication works. While it may +work for a machine user (i.e. an API client), it is impractical for a person—you do not want a +login prompt on every single page you navigate to! + +There is a solution - **sessions**. Using sessions the server can attach state to a set of +requests coming from the same client. They are built on top of cookies - the server sets a +cookie in the HTTP response (`Set-Cookie` header), the client (e.g. the browser) will store the +cookie and play it back to the server when sending new requests (using the `Cookie` header). + +We refer to the cookie used for sessions as a **session cookie**. Its content is called +**session key** (or **session ID**), while the state attached to the session is referred to as +**session state**. + +`actix-session` provides an easy-to-use framework to manage sessions in applications built on +top of Actix Web. [`SessionMiddleware`] is the middleware underpinning the functionality +provided by `actix-session`; it takes care of all the session cookie handling and instructs the +**storage backend** to create/delete/update the session state based on the operations performed +against the active [`Session`]. + +`actix-session` provides some built-in storage backends: ([`CookieSessionStore`], +[`RedisSessionStore`], and [`RedisActorSessionStore`]) - you can create a custom storage backend +by implementing the [`SessionStore`] trait. + +Further reading on sessions: +- [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265); +- [OWASP's session management cheat-sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html). + +# Getting started +To start using sessions in your Actix Web application you must register [`SessionMiddleware`] +as a middleware on your `App`: + +```no_run +use actix_web::{web, App, HttpServer, HttpResponse, Error}; +use actix_session::{Session, SessionMiddleware, storage::RedisSessionStore}; +use actix_web::cookie::Key; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // When using `Key::generate()` it is important to initialize outside of the + // `HttpServer::new` closure. When deployed the secret key should be read from a + // configuration file or environment variables. + let secret_key = Key::generate(); + + let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379") + .await + .unwrap(); + + HttpServer::new(move || + App::new() + // Add session management to your application using Redis for session state storage + .wrap( + SessionMiddleware::new( + redis_store.clone(), + secret_key.clone(), + ) + ) + .default_service(web::to(|| HttpResponse::Ok()))) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` + +The session state can be accessed and modified by your request handlers using the [`Session`] +extractor. Note that this doesn't work in the stream of a streaming response. + +```no_run +use actix_web::Error; +use actix_session::Session; + +fn index(session: Session) -> Result<&'static str, Error> { + // access the session state + if let Some(count) = session.get::("counter")? { + println!("SESSION value: {}", count); + // modify the session state + session.insert("counter", count + 1)?; + } else { + session.insert("counter", 1)?; + } + + Ok("Welcome!") +} +``` + +# Choosing A Backend + +By default, `actix-session` does not provide any storage backend to retrieve and save the state +attached to your sessions. You can enable: + +- a purely cookie-based "backend", [`CookieSessionStore`], using the `cookie-session` feature + flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["cookie-session"] } + ``` + +- a Redis-based backend via [`actix-redis`](https://docs.rs/actix-redis), + [`RedisActorSessionStore`], using the `redis-actor-session` feature flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-actor-session"] } + ``` + +- a Redis-based backend via [`redis-rs`](https://docs.rs/redis-rs), [`RedisSessionStore`], using + the `redis-rs-session` feature flag. + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-rs-session"] } + ``` + + Add the `redis-rs-tls-session` feature flag if you want to connect to Redis using a secured + connection: + + ```toml + [dependencies] + # ... + actix-session = { version = "...", features = ["redis-rs-session", "redis-rs-tls-session"] } + ``` + +You can implement your own session storage backend using the [`SessionStore`] trait. + +[`SessionStore`]: storage::SessionStore +[`CookieSessionStore`]: storage::CookieSessionStore +[`RedisSessionStore`]: storage::RedisSessionStore +[`RedisActorSessionStore`]: storage::RedisActorSessionStore +*/ #![forbid(unsafe_code)] #![deny(rust_2018_idioms, nonstandard_style)] diff --git a/justfile b/justfile index 4eea8add7..132914d8b 100644 --- a/justfile +++ b/justfile @@ -2,13 +2,16 @@ _list: @just --list # Format workspace. -fmt: +fmt: update-readmes cargo +nightly fmt npx -y prettier --write $(fd --hidden --extension=yml --extension=md) # Update READMEs from crate root documentation. -update-readmes: && fmt +update-readmes: cd ./actix-cors && cargo rdme --force + cd ./actix-session && cargo rdme --force + cd ./actix-identity && cargo rdme --force + npx -y prettier --write $(fd README.md) # Document crates in workspace. doc: