# How to Handle Cookies (Including Signed and Encrypted) with Topcoat's Cookie Jar API

> Learn to handle cookies with Topcoat's Cookie Jar API. Access signed and encrypted cookies using request helpers and configure secrets for automatic security.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-21

---

**Use the `cookies(cx)` and `cookies_mut(cx)` helpers to access the request-scoped `CookieJar`, enabling the `signed` and `private` Cargo features for HMAC verification and encryption, and configure a `SecretKey` in the app context to automatically secure cookie values.**

The `tokio-rs/topcoat` framework provides a **request-scoped cookie jar** that gives you ergonomic, type-safe access to HTTP cookies inside request handlers. Unlike traditional middleware-based approaches, Topcoat exposes cookie functionality through plain functions that accept the request context (`&Cx`), allowing you to read, write, sign, and encrypt cookies with minimal boilerplate. This article explains how to use the cookie jar API to handle standard, signed, and encrypted cookies based on the implementation in [`crates/topcoat/src/cookie.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/cookie.rs).

## Accessing the Cookie Jar

Topcoat stores the `CookieJar` inside the request context (`Cx`) and exposes it via two helper functions:

- **`cookies(cx)`** – Returns a read-only reference to the jar for retrieving cookies.
- **`cookies_mut(cx)`** – Returns a mutable reference for inserting or removing cookies.

Both functions are re-exported from [`crates/topcoat/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/lib.rs) and provide immediate access without requiring middleware registration.

### Reading Cookies

To retrieve a cookie by name, use the `get` method, which returns an `Option<Cookie>` with the URL-decoded value:

```rust
use topcoat::prelude::*;

async fn read_theme(cx: &Cx) -> Result<impl IntoResponse> {
    let theme = cookies(cx).get("theme")
        .map(|c| c.value().to_string())
        .unwrap_or_else(|| "light".to_string());
    
    Ok(format!("Current theme: {}", theme))
}

```

### Writing and Removing Cookies

To set a cookie, construct a `Cookie` using the builder pattern and insert it into the mutable jar. You can configure standard attributes like `path`, `domain`, `secure`, `http_only`, and `same_site`:

```rust
async fn set_theme(cx: &Cx) -> Result<impl IntoResponse> {
    let mut jar = cookies_mut(cx);
    jar.insert(
        Cookie::build("theme", "dark")
            .http_only(true)
            .secure(true)
            .path("/")
            .max_age(time::Duration::days(7))
            .finish(),
    );
    
    Ok(StatusCode::OK)
}

```

To delete a cookie, call `jar.remove(name)`, which sets an expiration date in the past.

## Configuring Security Keys

Before using signed or encrypted cookies, you must register a **32-byte secret key** in the application context. The `SecretKey` type is defined in [`crates/topcoat/src/crypto.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/crypto.rs) and consumed by the cookie module.

Configure the key once during application startup:

```rust
use topcoat::crypto::SecretKey;

fn configure_app(app: &mut App) {
    let secret = SecretKey::new(b"very-secret-32-bytes-key!!");
    app.app_context(secret);
}

```

The cookie jar automatically retrieves this key from the context using `app_context::<SecretKey>(cx)` when signing or decrypting values.

## Working with Signed Cookies

Enable the **`signed`** Cargo feature to add HMAC signature verification to your cookies. Signed cookies prevent tampering by appending a cryptographic signature to the value; if the client modifies the payload, verification fails.

### Setting Signed Cookies

Use `insert_signed` to automatically sign the value before sending it to the client:

```rust
async fn set_csrf(cx: &Cx) -> Result<impl IntoResponse> {
    let token = generate_csrf_token();
    cookies_mut(cx).insert_signed("csrf_token", token);
    Ok(StatusCode::OK)
}

```

### Verifying Signed Cookies

Use `get_signed` to retrieve and validate the signature in one operation. The method returns `None` if the cookie is missing or the signature is invalid:

```rust
async fn verify_csrf(cx: &Cx) -> Result<impl IntoResponse> {
    let token = cookies(cx).get_signed::<String>("csrf_token")
        .ok_or_else(|| Error::bad_request("Invalid or missing CSRF token"))?;
    
    // Proceed with token verification...
    Ok(StatusCode::OK)
}

```

The signed cookie implementation in [`crates/topcoat/src/cookie.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/cookie.rs) handles the HMAC computation transparently, ensuring the signature is stripped from the returned value while validated on every read.

## Working with Encrypted (Private) Cookies

Enable the **`private`** Cargo feature for **encryption at rest**. Private cookies encrypt the value using the secret key before base64-encoding it for transport, ensuring that users cannot inspect the cookie contents.

### Setting Private Cookies

Use `insert_private` to encrypt sensitive data:

```rust
async fn login(cx: &Cx, user_id: u64) -> Result<impl IntoResponse> {
    cookies_mut(cx).insert_private("user_id", user_id);
    Ok(StatusCode::OK)
}

```

### Reading Private Cookies

Use `get_private` to decrypt the value automatically:

```rust
async fn dashboard(cx: &Cx) -> Result<impl IntoResponse> {
    let user_id = cookies(cx).get_private::<u64>("user_id")
        .ok_or_else(|| Error::unauthorized("Session required"))?;
    
    Ok(format!("Welcome, user {}", user_id))
}

```

The encryption/decryption logic resides in the `CookieJar` implementation in [`crates/topcoat/src/cookie.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/cookie.rs), which uses the `SecretKey` configured in the app context.

## Using Typed Cookies with CookieStore

For structured data, Topcoat provides **`CookieStore<T>`**, a generic wrapper that serializes any `serde`-compatible type to JSON before storage. This eliminates manual string parsing and provides compile-time type safety.

Define your data structure and use the typed store:

```rust
use topcoat::cookie::CookieStore;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Preferences {
    dark_mode: bool,
    language: String,
}

async fn save_prefs(cx: &Cx) -> Result<impl IntoResponse> {
    let store = CookieStore::<Preferences>::new("prefs");
    let prefs = Preferences {
        dark_mode: true,
        language: "en".to_string(),
    };
    
    // Automatically signs if the 'signed' feature is enabled
    store.set_signed(cx, &prefs)?;
    Ok(StatusCode::OK)
}

async fn load_prefs(cx: &Cx) -> Result<impl IntoResponse> {
    let store = CookieStore::<Preferences>::new("prefs");
    let prefs = store.get_signed(cx)?
        .unwrap_or_else(|| Preferences { dark_mode: false, language: "en".to_string() });
    
    Ok(Json(prefs))
}

```

The `CookieStore<T>` struct is defined in [`crates/topcoat/src/cookie.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/cookie.rs) and delegates signing or encryption to the underlying `CookieJar` based on the enabled feature flags.

## Summary

- **Access cookies** via `cookies(cx)` for reading and `cookies_mut(cx)` for writing, both available anywhere you have `&Cx`.
- **Enable features** by adding `signed` and/or `private` to your [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml) to unlock `get_signed`/`insert_signed` and `get_private`/`insert_private` methods.
- **Configure keys** once at startup using `app.app_context(SecretKey::new(...))` from [`crates/topcoat/src/crypto.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/src/crypto.rs); the jar handles all cryptographic operations automatically.
- **Use typed storage** with `CookieStore<T>` to serialize complex structures without manual JSON handling.
- **No middleware required**—Topcoat's function-based design keeps cookie handling explicit and composable.

## Frequently Asked Questions

### How do I enable signed or encrypted cookies in Topcoat?

Add the corresponding feature flags to your [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml) dependencies:

```toml
[dependencies]
topcoat = { version = "...", features = ["signed", "private"] }

```

Once enabled, the `CookieJar` methods `insert_signed`, `get_signed`, `insert_private`, and `get_private` become available. You must also configure a `SecretKey` via `app.app_context()` before handling requests.

### What happens if a signed cookie is tampered with?

The `get_signed` method returns `None` if the HMAC signature does not match the payload. This indicates either the cookie was modified by the client or the signing key has changed. Your handler should treat this as an invalid or missing cookie and respond appropriately (e.g., returning an error or resetting the session).

### Can I use both signed and encrypted cookies in the same application?

Yes. You can enable both the `signed` and `private` features simultaneously. Use `insert_signed` for data that needs integrity but can be visible (like a user preference), and `insert_private` for sensitive data that must remain confidential (like user IDs or session tokens). Both methods share the same `SecretKey` configured in the app context.

### How do I delete a signed or encrypted cookie?

Use the standard `remove` method on the mutable jar:

```rust
cookies_mut(cx).remove("session_id");

```

This works for all cookie types (plain, signed, or private) because it sets the cookie's expiration to the past, instructing the browser to delete it regardless of the payload format.