tower_surf/
lib.rs

1//! ## 🏄‍♂️ Overview
2//!
3//! This crate uses the [Double Submit Cookie Pattern][owasp-double-submit] to mitigate CSRF.
4//!
5//! ### How it works
6//!
7//! - **Secret key**: You provide a **secret key** used to sign CSRF tokens. This secret is secured by [secstr][crate-secstr] and only
8//! in memory as plaintext during the signing and validating processes.
9//! For more information on managing your secret key, see [OWASP's Cryptographic Storage Cheat Sheet][owasp-cryptographic-storage]).
10//! - **Token creation**:
11//!   - We generate a **message** by combining a unique **session identifier** with a cryptographically secure **random value** (using the [`rand`][crate-rand] crate).
12//!   - We then create an **signature** using the **secret key** and the **message**.
13//!   - The token is formed by concatenating the **signature** and the **message**.
14//! - **Token storage**:
15//!   - The server sends the token to the client in two ways:
16//!     - As a cookie (handled by us).
17//!     - In the header of subsequent requests (handled by you).
18//! - **Token validation**:
19//!   - For each incoming request that would mutate state:
20//!     - We extract the token from the request headers.
21//!     - We split the token into the **signature** and the **message**.
22//!     - We recalculate the **signature** using the **secret key** and compare them.
23//!   - If the **signature** is valid and the token matches the value stored in the cookie, the request is allowed to proceed.
24//!
25//! ### Cookies
26//!
27//! By default, the cookies are set to `HTTPOnly`, `SameSite: Strict`, and `Secure`.
28//!
29//! ## 📦 Install
30//!
31//! ```toml
32//! [dependencies]
33//! tower-surf = "0.3.0"
34//! ```
35//!
36//! ## 🗝️ Usage
37//!
38//! ### With [`axum`][crate-axum]
39//!
40//! ```rust, no_run
41//! use std::net::SocketAddr;
42//!
43//! use axum::{routing::get, Router};
44//! use http::StatusCode;
45//! use tower_surf::{Surf, Token};
46//!
47//! #[tokio::main]
48//! async fn main() {
49//!     let app = Router::new()
50//!         .route("/login", get(login)).route("/logout", get(logout))
51//!         .layer(Surf::new("secret-key").secure(false));
52//!
53//!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
54//!     let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
55//!
56//!     axum::serve(listener, app.into_make_service())
57//!         .await
58//!         .unwrap();
59//! }
60//!
61//! async fn login(token: Token) -> Result<StatusCode, StatusCode> {
62//!     token.set("unique-session-id").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
63//!
64//!     Ok(StatusCode::OK)
65//! }
66//!
67//! async fn logout(token: Token) -> StatusCode {
68//!     token.reset();
69//!
70//!     StatusCode::OK
71//! }
72//! ```
73//!
74//!
75//! ## 🥰 Thank you
76//!
77//! - I read a lot of the [tower-sessions](https://github.com/maxcountryman/tower-sessions) codebase to figure out how to make a tower project.
78//! - The [tokio community](https://discord.com/invite/tokio) answered a lot of silly questions.
79//!
80//! [crate-axum]: https://github.com/tokio-rs/axum
81//! [crate-rand]: https://github.com/rust-random/rand
82//! [crate-tower]: https://github.com/tower-rs/tower
83//! [crate-secstr]: https://codeberg.org/valpackett/secstr
84//! [examples]: https://github.com/its-danny/tower-surf/tree/main/examples
85//! [owasp-cryptographic-storage]: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
86//! [owasp-double-submit]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern
87
88pub use error::Error;
89pub use surf::Surf;
90pub use token::Token;
91
92mod error;
93mod guard;
94mod surf;
95mod token;
96
97#[cfg(feature = "axum")]
98mod extract;