1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
//! ## 🏄♂️ Overview
//!
//! This crate uses the [Double Submit Cookie Pattern][owasp-double-submit] to mitigate CSRF.
//!
//! ### How it works
//!
//! - **Secret key**: You provide a **secret key** used to sign CSRF tokens (See: [OWASP's Cryptographic Storage Cheat Sheet][owasp-cryptographic-storage]).
//! - **Token creation**:
//! - We generate a **message** by combining a unique **session identifier** with a cryptographically secure **random value** (using the [`rand`][crate-rand] crate).
//! - We then create an **signature** using the **secret key** and the **message**.
//! - The token is formed by concatenating the **signature** and the **message**.
//! - **Token storage**:
//! - The server sends the token to the client in two ways:
//! - As a cookie (handled by us).
//! - In the header of subsequent requests (handled by you).
//! - **Token validation**:
//! - For each incoming request:
//! - We extract the token from the request headers.
//! - We split the token into the **signature** and the **message**.
//! - We recalculate the **signature** using the **secret key** and compare them.
//! - If the **signature** is valid and the token matches the value stored in the cookie, the request is allowed to proceed.
//!
//! ### Cookies
//!
//! By default, the cookies are set to `HTTPOnly`, `SameSite: Strict`, and `Secure`.
//!
//! ## 🗝️ Usage
//!
//! ### With [`axum`][crate-axum]
//!
//! ```rust, no_run
//! use std::net::SocketAddr;
//!
//! use axum::{routing::get, Router};
//! use http::StatusCode;
//! use tower_surf::{Surf, Token};
//!
//! #[tokio::main]
//! async fn main() {
//! let app = Router::new()
//! .route("/login", get(login)).route("/logout", get(logout))
//! .layer(Surf::new("secret-key").secure(false));
//!
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
//! let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
//!
//! axum::serve(listener, app.into_make_service())
//! .await
//! .unwrap();
//! }
//!
//! async fn login(token: Token) -> Result<StatusCode, StatusCode> {
//! token.set("unique-session-id").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
//!
//! Ok(StatusCode::OK)
//! }
//!
//! async fn logout(token: Token) -> StatusCode {
//! token.reset();
//!
//! StatusCode::OK
//! }
//! ```
//!
//! [crate-axum]: https://github.com/tokio-rs/axum
//! [crate-rand]: https://github.com/rust-random/rand
//! [crate-tower]: https://github.com/tower-rs/tower
//! [owasp-cryptographic-storage]: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
//! [owasp-double-submit]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern
//! [owasp-login-csrf]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#possible-csrf-vulnerabilities-in-login-forms
pub use error::Error;
pub use surf::Surf;
pub use token::Token;
mod error;
mod guard;
mod surf;
mod token;
#[cfg(feature = "axum")]
mod extract;