Cookbook / Auth and sessions

Sessions and broadcast on Redis

Share sessions and live updates across every instance with axeon-redis.

MemoryStore keeps sessions in one process, so they vanish on a restart and do not follow a user between instances. axeon-redis stores them in Redis instead, and also provides a backend for broadcasting messages across instances. It needs Redis 6.2 or newer.

Terminal
cargo add axeon-redis
src/main.rs
use axeon::broadcast::Hub;
use axeon::cookies::{CookieManager, Key};
use axeon::session::Sessions;
use axeon::{Server, ServerError};
use axeon_redis::{RedisBroadcast, RedisSessionStore};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".into());

    // One multiplexed connection that reconnects by itself.
    let redis = axeon_redis::connect(&url).await?;

    let mut app = Server::new();
    app.middleware(CookieManager::new().key(Key::generate()))
        .middleware(
            Sessions::new(RedisSessionStore::new(redis.clone()).prefix("myapp:session:"))
                .ttl(Duration::from_secs(24 * 3600))
                .secure(!cfg!(debug_assertions)),
        );

    // Live updates that reach clients on every instance.
    app.state(Hub::new(RedisBroadcast::connect(&url).await?.max_len(1000)));

    app.post("/visit", |req| async move {
        let session = req.session()?;
        let visits: u64 = session.get("visits").unwrap_or(0) + 1;
        session.insert("visits", visits)?;
        Ok::<_, ServerError>(format!("visit {}", visits))
    });

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
    app.serve_with_shutdown(listener, axeon::shutdown_signal()).await?;
    Ok(())
}
Terminal
docker run --rm -p 6379:6379 redis:7
curl -c jar -b jar -X POST http://127.0.0.1:3000/visit   # visit 1
curl -c jar -b jar -X POST http://127.0.0.1:3000/visit   # visit 2, even after a restart

Notes#

  • Each session is one key holding JSON, expiring with the session's TTL. Keys are the prefix plus a SHA-256 hash of the session ID, so anyone who can read Redis cannot turn a key back into a usable cookie.
  • While Redis is down, requests that use a session fail immediately with 500 rather than waiting; other routes are unaffected. When Redis returns, requests work again with no restart.
  • Add the tls feature for rediss:// URLs, as managed Redis services require.
  • The redis crate is re-exported as axeon_redis::redis for caching and other commands.
  • This recipe has no CSRF middleware to stay short. Browser apps need it: see Cookies, sessions and CSRF.