# How Sessions and Authentication Work in Topcoat: A Developer's Guide

> Discover how Topcoat's stateless session system and authentication work. Learn about cryptographically secure tokens and SHA-256 hashes for robust CSRF protection.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: tutorial
- Published: 2026-07-31

---

**Topcoat generates cryptographically secure tokens for client-side storage while requiring applications to persist only SHA-256 hashes, creating a stateless session system with built-in CSRF protection.**

Sessions and authentication in Topcoat rely on a minimalist, security-first architecture implemented across the `crates/topcoat-session` directory. According to the tokio-rs/topcoat source code, the framework handles token generation, transport, and validation while delegating all session data persistence to the application layer. This design ensures that raw authentication secrets never touch server-side storage, reducing impact potential in the event of database breaches.

## Core Session Architecture

Topcoat's session system revolves around three fundamental primitives defined in [`crates/topcoat-session/src/token.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/token.rs) and [`crates/topcoat-session/src/session.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/session.rs).

### Tokens and TokenHashes

The framework generates **32-byte cryptographically-secure random values** represented by the `Token` struct. These tokens are held exclusively by clients and are never stored raw on the server.

Instead, applications persist `TokenHash` values—SHA-256 hashes of the original tokens—as defined in [`crates/topcoat-session/src/token/hash.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/token/hash.rs). This hash-based approach means that even if your database leaks, attackers cannot reconstruct valid session tokens without performing an infeasible brute-force attack against the 32-byte key space.

### Session Records

A `Session` struct contains two fields: `token_hash` and `expires_at`. When you initiate authentication, the `start` function returns this struct for your application to store alongside user records. The framework deliberately omits storage logic, allowing you to use any database or cache backend.

### Token Storage Abstraction

The `TokenStore` trait in [`crates/topcoat-session/src/token/store.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/token/store.rs) abstracts how tokens travel between client and server. The default `CookieTokenStore` implementation uses hardened cookies with the `__Host-` prefix, `Secure`, `HttpOnly`, and `SameSite=Lax` attributes, but custom implementations can use headers or alternative transport mechanisms.

## Session Lifecycle API

All session operations require a request context `&Cx` and are implemented in [`crates/topcoat-session/src/session.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/session.rs). The token is read once per request and cached, ensuring that mutations immediately affect subsequent code.

### Starting Sessions

The `session::start(cx).await?` function mints a fresh token, sends it to the client via the configured `TokenStore`, and returns a `Session` struct containing the hash and expiration. This operation protects against session fixation attacks by ensuring a new token is generated even if the client presented an existing one.

### Validating Requests

Use `session::token_hash(cx).await?` to retrieve the hash of the token presented with the current request. This returns `Option<TokenHash>`, allowing you to look up the session in your database and verify it hasn't expired.

### Terminating Sessions

Calling `session::stop(cx).await?` instructs the client to discard its token and returns `Option<TokenHash>` so you can delete the corresponding database record. This completes the logout lifecycle.

### Sliding Expiration and Rotation

For active sessions, `session::refresh(cx).await?` re-issues the same token with a new expiration timestamp, implementing sliding expiration patterns. When privilege levels change, `session::rotate(cx).await?` replaces the token entirely, returning a `Rotation` struct containing both the new `Session` and the revoked `TokenHash` for atomic database updates.

## Configuration and Setup

Configure sessions using `SessionConfig` from [`crates/topcoat-session/src/config.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/config.rs). The builder pattern allows customization of cookie names, lifetimes, and token storage backends.

```rust
use std::time::Duration;
use topcoat::session::{SessionConfig, cookie::CookieTokenStore};

let config = SessionConfig::builder()
    .token_store(CookieTokenStore::new().name("session_id"))
    .lifetime(Duration::from_hours(24 * 14))
    .build();

```

Apply sessions to your router using extension traits:

```rust
use topcoat::{
    cookie::RouterBuilderCookieExt,
    router::Router,
    session::{RouterBuilderSessionExt, SessionConfig},
};

let router = Router::builder()
    .cookies()
    .sessions(SessionConfig::default())
    .build();

```

## CSRF Protection with OriginLayer

When you enable sessions via `.sessions()`, Topcoat automatically registers `OriginLayer` from [`crates/topcoat-session/src/origin.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/origin.rs). This middleware validates the `Sec-Fetch-Site` header (falling back to `Origin`) for any non-idempotent request method (POST, PUT, DELETE, etc.), rejecting cross-origin attempts with a 403 status unless the origin is explicitly trusted.

## Implementation Examples

The following patterns from [`examples/session/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/session/src/main.rs) demonstrate complete authentication workflows.

### Login Handler

```rust
use topcoat::{Result, context::Cx, router::{error::SeeOther, see_other}, session};

#[route(POST "/login")]
async fn login(cx: &Cx) -> Result<SeeOther> {
    // …authenticate user credentials…
    let session = session::start(cx).await?;
    // Store session.token_hash and session.expires_at in your database
    persist_session(cx, &user, &session).await?;
    Ok(see_other("/"))
}

```

### Current User Resolution

```rust
use topcoat::{Result, context::Cx, session};

async fn current_user(cx: &Cx) -> Result<Option<User>> {
    let Some(hash) = session::token_hash(cx).await? else {
        return Ok(None);
    };
    // Query your database for the hash; return None if missing or expired
    load_user_by_hash(cx, &hash).await
}

```

### Logout Handler

```rust
use topcoat::{Result, context::Cx, router::{error::SeeOther, see_other}, session};

#[route(POST "/logout")]
async fn logout(cx: &Cx) -> Result<SeeOther> {
    if let Some(hash) = session::stop(cx).await? {
        delete_session_record(cx, &hash).await?;
    }
    Ok(see_other("/"))
}

```

### Token Rotation for Privilege Changes

```rust
use topcoat::{Result, context::Cx, session};

async fn elevate_privileges(cx: &Cx) -> Result<()> {
    if let Some(rot) = session::rotate(cx).await? {
        // Atomically replace the old hash with the new one
        rekey_session(cx, &rot.revoked, &rot.session).await?;
    }
    Ok(())
}

```

## Summary

- **Topcoat uses a hash-based session model** where only SHA-256 hashes of 32-byte tokens are stored server-side, implemented in [`crates/topcoat-session/src/token/hash.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/token/hash.rs).
- **The lifecycle API** (`start`, `stop`, `refresh`, `rotate`) in [`crates/topcoat-session/src/session.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/session.rs) provides stateless operations that delegate persistence to your application.
- **Default security features** include hardened cookies (`__Host-` prefix, `Secure`, `HttpOnly`, `SameSite=Lax`) and automatic CSRF protection via `OriginLayer`.
- **Token rotation** allows secure privilege escalation by atomically replacing tokens without breaking session continuity.
- **Zero storage assumptions** mean you maintain full control over session data, expiration logic, and database schema.

## Frequently Asked Questions

### How does Topcoat prevent session fixation attacks?

Topcoat's `session::start` function always generates a fresh 32-byte token, even if the client already presents a valid token from a previous session. This ensures that authenticating into the application invalidates any pre-existing session identifiers, preventing attackers from fixing session IDs before user login.

### Where should I store session data in Topcoat?

Topcoat intentionally provides no built-in session storage. You must implement persistence logic in your application code, typically by storing the `token_hash` and `expires_at` fields from the `Session` struct alongside your user records in a database or cache. This design keeps cryptographic material out of your storage layer while giving you complete control over data retention policies.

### What happens if I don't rotate tokens after privilege changes?

If you fail to call `session::rotate` when escalating user privileges (e.g., after MFA verification or role elevation), the original token remains valid with its original security context. Rotation ensures that tokens issued before privilege changes are cryptographically invalidated, requiring the client to present the new token that reflects the updated authorization state.

### How does Topcoat protect against CSRF attacks?

When sessions are enabled, Topcoat automatically inserts `OriginLayer` from [`crates/topcoat-session/src/origin.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-session/src/origin.rs) into the middleware stack. This layer validates the `Sec-Fetch-Site` header for state-changing requests, rejecting any cross-origin POST, PUT, or DELETE attempts unless the origin is explicitly whitelisted in the `SessionConfig`.