From 38015937218ac0f2671e285ef6b65f1bcfe27a59 Mon Sep 17 00:00:00 2001 From: Coco Liliace Date: Fri, 10 Jul 2026 21:51:46 +0800 Subject: [PATCH 1/3] Set Secure on the session removal cookie Browsers reject `__Host-`/`__Secure-`-prefixed cookies without Secure, so we must set session_config.secure on the cookie removal path. --- src/service.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/service.rs b/src/service.rs index 23c4ba4..38aab36 100644 --- a/src/service.rs +++ b/src/service.rs @@ -245,6 +245,11 @@ where cookie.set_domain(domain); } + // Secure must be set because the removal is a Set-Cookie, + // and browsers reject `__Host-`/`__Secure-`-prefixed cookies without + // Secure. + cookie.set_secure(session_config.secure); + cookie_controller.remove(&cookies, cookie); } @@ -565,6 +570,17 @@ mod tests { Ok(Response::new(Body::empty())) } + async fn flush_handler(req: Request) -> anyhow::Result> { + let session = req + .extensions() + .get::() + .ok_or(anyhow!("Missing session"))?; + + session.flush().await?; + + Ok(Response::new(Body::empty())) + } + #[tokio::test] async fn basic_service_test() -> anyhow::Result<()> { let session_store = MemoryStore::default(); @@ -890,6 +906,37 @@ mod tests { Ok(()) } + #[tokio::test] + async fn secure_removal_test() -> anyhow::Result<()> { + let session_store = MemoryStore::default(); + let session_layer = SessionManagerLayer::new(session_store.clone()).with_secure(true); + let svc = ServiceBuilder::new() + .layer(session_layer) + .service_fn(handler); + + let req = Request::builder().body(Body::empty())?; + let res = svc.oneshot(req).await?; + + let session_cookie = res + .headers() + .get(http::header::SET_COOKIE) + .ok_or(anyhow!("Missing session cookie"))?; + + let flush_layer = SessionManagerLayer::new(session_store).with_secure(true); + let flush_svc = ServiceBuilder::new() + .layer(flush_layer) + .service_fn(flush_handler); + + let req = Request::builder() + .header(http::header::COOKIE, session_cookie) + .body(Body::empty())?; + let res = flush_svc.oneshot(req).await?; + + assert!(cookie_value_matches(&res, |s| s.contains("Secure"))); + + Ok(()) + } + #[tokio::test] async fn path_test() -> anyhow::Result<()> { let session_store = MemoryStore::default(); From 979c16a78c3d3dac3c4f30fe500e1fb41595723d Mon Sep 17 00:00:00 2001 From: Coco Liliace Date: Mon, 13 Jul 2026 22:55:40 +0800 Subject: [PATCH 2/3] Clarify the secure field on cookie removal --- src/service.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/service.rs b/src/service.rs index 38aab36..8f7c0b8 100644 --- a/src/service.rs +++ b/src/service.rs @@ -245,9 +245,8 @@ where cookie.set_domain(domain); } - // Secure must be set because the removal is a Set-Cookie, - // and browsers reject `__Host-`/`__Secure-`-prefixed cookies without - // Secure. + // Preserve the configured Secure attribute. Otherwise + // `__Host-`/`__Secure-`-prefixed cookies without Secure may be rejected. cookie.set_secure(session_config.secure); cookie_controller.remove(&cookies, cookie); From 5639770c3c08b545c7abfd322bd59269908fb88a Mon Sep 17 00:00:00 2001 From: Coco Liliace Date: Tue, 14 Jul 2026 00:06:48 +0800 Subject: [PATCH 3/3] Add cookie removal test for secure=false --- src/service.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/service.rs b/src/service.rs index 8f7c0b8..bf5c858 100644 --- a/src/service.rs +++ b/src/service.rs @@ -936,6 +936,43 @@ mod tests { Ok(()) } + #[tokio::test] + async fn insecure_removal_test() -> anyhow::Result<()> { + let session_store = MemoryStore::default(); + let session_layer = SessionManagerLayer::new(session_store.clone()).with_secure(false); + let svc = ServiceBuilder::new() + .layer(session_layer) + .service_fn(handler); + + let req = Request::builder().body(Body::empty())?; + let res = svc.oneshot(req).await?; + + let session_cookie = res + .headers() + .get(http::header::SET_COOKIE) + .ok_or(anyhow!("Missing session cookie"))?; + + let flush_layer = SessionManagerLayer::new(session_store).with_secure(false); + let flush_svc = ServiceBuilder::new() + .layer(flush_layer) + .service_fn(flush_handler); + + let req = Request::builder() + .header(http::header::COOKIE, session_cookie) + .body(Body::empty())?; + let res = flush_svc.oneshot(req).await?; + + let removal_cookie = res + .headers() + .get(http::header::SET_COOKIE) + .ok_or(anyhow!("Missing removal cookie"))?; + let removal_cookie = Cookie::parse(removal_cookie.to_str()?)?; + + assert_eq!(removal_cookie.secure(), Some(false)); + + Ok(()) + } + #[tokio::test] async fn path_test() -> anyhow::Result<()> { let session_store = MemoryStore::default();