How to Use Signed and Encrypted Cookies in Topcoat

Use the cookie! macro with the signed or private attributes to protect cookie data, and register a SecretKey in the application context to enable HMAC signatures or AES-GCM encryption.

The tokio-rs/topcoat framework provides a unified API for handling signed and encrypted cookies in Topcoat through the declarative cookie! macro and the underlying CookieJar abstraction. By leveraging application context injection, you can enforce data integrity and confidentiality without implementing cryptographic primitives manually.

Topcoat implements two distinct protection strategies in crates/topcoat/src/cookie.rs, each designed for specific security requirements.

Signed Cookies (Integrity Protection)

Signed cookies store the plaintext value on the client while appending an HMAC-based cryptographic signature. The SignedCookie type handles this verification automatically, detecting any tampering attempts when the cookie is read. According to the Topcoat source code, the signature is verified on each request using the secret key retrieved from app_context::<SecretKey>(cx).

Private Cookies (Confidentiality)

Private (encrypted) cookies ensure confidentiality by encrypting the value before transmission. The PrivateCookie struct in the same file uses authenticated encryption (AES-GCM) to prevent both disclosure and modification. The macro adds the private attribute to trigger this encryption mode, storing only the ciphertext on the client.

Configuring the Secret Key

Both mechanisms share the same 256-bit key material, which you must register in the application context at startup. This key is typically loaded from environment variables or secure configuration stores.

use topcoat::prelude::*;

#[derive(Clone)]
struct SecretKey(Vec<u8>);

fn main() {
    topcoat::run(|mut app| {
        let key = std::env::var("TOPCOAT_COOKIE_KEY")
            .expect("TOPCOAT_COOKIE_KEY must be set")
            .into_bytes();
        
        // Register the key in the app context
        app.app_context(SecretKey(key));
    });
}

Setting Signed and Encrypted Cookies

Use the cookie! macro declaratively to specify the protection level. Topcoat automatically signs or encrypts the value and sets the appropriate response headers.

Setting a signed cookie:

#[topcoat::get("/set-signed")]
async fn set_signed(cx: &Cx) -> impl IntoResponse {
    // The `signed` attribute triggers HMAC signing
    let signed = cookie!("session_id", "abc123", signed);
    cookies(cx).add(signed);
    "Signed cookie set"
}

Setting an encrypted (private) cookie:

#[topcoat::get("/set-private")]
async fn set_private(cx: &Cx) -> impl IntoResponse {
    // The `private` attribute triggers AES-GCM encryption
    let private = cookie!("auth_token", "secret-token", private);
    cookies(cx).add(private);
    "Private cookie set"
}

Reading and Verifying Cookies

Access the CookieJar via cookies(cx) to read values. Topcoat transparently verifies signatures or decrypts values before returning them to your handler, returning None if verification fails or if the cookie is missing.

#[topcoat::get("/read")]
async fn read_cookie(cx: &Cx) -> impl IntoResponse {
    match cookies(cx).get("session_id") {
        Some(value) => format!("Verified value: {}", value),
        None => "No valid cookie found".into()
    }
}

Deleting Cookies

Remove cookies by calling the remove method on the CookieJar instance:

#[topcoat::get("/logout")]
async fn logout(cx: &Cx) -> impl IntoResponse {
    cookies(cx).remove("session_id");
    cookies(cx).remove("auth_token");
    "Cookies cleared"
}

Key Source Files

The cookie implementation spans the following locations in the tokio-rs/topcoat repository:

Summary

  • Register a secret key using app.app_context(SecretKey(key)) at application startup to enable cryptographic operations.
  • Use cookie!(..., signed) for integrity protection (HMAC) and cookie!(..., private) for confidentiality (AES-GCM).
  • Access cookies via cookies(cx).get("name"), which automatically verifies signatures or decrypts values using the context-registered key.
  • Reference implementation resides in crates/topcoat/src/cookie.rs, with additional documentation available in crates/topcoat/docs/cookie.md.

Frequently Asked Questions

What is the difference between signed and private cookies in Topcoat?

Signed cookies ensure data integrity by appending an HMAC signature to the plaintext value, preventing tampering but allowing the client to read the content. Private cookies encrypt the entire value using AES-GCM, ensuring confidentiality so the client cannot read the data, while also providing integrity protection.

How do I rotate the secret key without invalidating existing sessions?

The current implementation in crates/topcoat/src/cookie.rs uses a single key from the application context. To rotate keys, you would typically deploy the new key while accepting the old key for verification during a transition period, though the source code suggests using the context-retrieved key directly via app_context::<SecretKey>(cx).

Does Topcoat use separate keys for signing and encryption?

No. As implemented in the source code, both signed and private cookie modes share the same 256-bit key material supplied via the application context. The SignedCookie and PrivateCookie types derive their cryptographic material from the same SecretKey registration.

When you call cookies(cx).get("name") on a tampered signed cookie, Topcoat detects the signature mismatch during HMAC verification in crates/topcoat/src/cookie.rs and returns None to your handler, effectively treating the cookie as if it were never set.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →