Setting and Reading Session Cookies in Rust with Actix Web
Tags: actix, rust, webdev
Published: 2023-07-18 19:34:46

---
title: Setting and Reading Session Cookies in Rust with Actix Web
published: true
date: 2023-07-17 00:00:00 UTC
tags: ["actix", "rust", "webdev"]
canonical_url: https://webbureaucrat.gitlab.io/articles/setting-and-reading-session-cookies-in-rust-with-actix-web/
---

In case it's not readily apparent, I've been having some trouble picking a backend stack recently, but, at the risk of jinxing it, I really think Rust is the backend for me, and Actix Web is a blindingly fast MVC framework for Rust with an elegant and functional middleware system. As with all web apps, my first challenge is persisting and managing state between requests, so let's get into user session management in Actix Web.

### Starting with Hello World in Rust with Actix Web

For completeness, I'm going to start with `cargo new`. Feel free to skip ahead.

You can start with `cargo new actix-kata`.

If you enter your directory, you should find a "Hello, world" program. If you`cargo run` it, you should get "Hello, world" printed to the screen.

To add Actix Web to your project, run `cargo add actix-web`. If you `cargo build` or `cargo run` again, it should install Actix Web and all its dependencies.

Then replace _src/main.rs_ with the following:

```rust
use actix_web;

#[actix_web::get("/")]
async fn index() -> impl actix_web::Responder {
    actix_web::HttpResponse::Ok().body("Hello, world")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    actix_web::HttpServer::new( || {
	actix_web::App::new()
	    .service(index)
    })
	.bind(("127.0.0.1", 3000))?
	.run()
	.await
}
```

### Getting started with `actix-session`

`actix-session` is it's own Rust crate, separate from `actix-web`. It has a feature flag for cookie sessions. To use it, install it with `cargo add actix-session --features cookie-session`.

Our goal will be to build out a `SessionMiddleware<CookieSessionStore>`. We can do that with a[`SessionMiddlewareBuilder`](https://docs.rs/actix-session/latest/actix_session/config/struct.SessionMiddlewareBuilder.html). To put a fine point on it, let's break it out into its own function.

```rust
use actix_session::{ SessionMiddleware };
use actix_session::storage::{ CookieSessionStore };
use actix_web;
use actix_web::cookie::{ Key };

#[actix_web::get("/")]
async fn index() -> impl actix_web::Responder {
    actix_web::HttpResponse::Ok().body("Hello, world")
}

fn session_middleware() -> SessionMiddleware<CookieSessionStore> {
    SessionMiddleware::builder(
	    CookieSessionStore::default(), Key::from(&[0; 64])
    )
	.build()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    actix_web::HttpServer::new( || {
	actix_web::App::new()
	    .wrap(session_middleware()) //register session middleware
	    .service(index)
    })
    .bind(("127.0.0.1", 3000))?
    .run()
    .await
}
```

This accepts all defaults and registers the session middleware in the application.

### Building out a cookie session in Actix Web

Now let's fill in some details using the provided builder methods. (Some of these are set by default, but in the interest of a good demonstration, let's make as much explicit as we can.)

Importantly, many possible configuration items are matters of security both for the application and for the user, so it is a very good idea to familiarize oneself with each item in the documentation.

```rust
use actix_session::{ SessionMiddleware, Session };
use actix_session::config::{ BrowserSession, CookieContentSecurity };
use actix_session::storage::{ CookieSessionStore };
use actix_web;
use actix_web::cookie::{ Key, SameSite };

#[actix_web::get("/")]
async fn index() -> impl actix_web::Responder {
    actix_web::HttpResponse::Ok().body("Hello, world")
}

fn session_middleware() -> SessionMiddleware<CookieSessionStore> {
    SessionMiddleware::builder(
        CookieSessionStore::default(), Key::from(&[0; 64])
    )
	.cookie_name(String::from("my-kata-cookie")) // arbitrary name
	.cookie_secure(true) // https only
	.session_lifecycle(BrowserSession::default()) // expire at end of session
	.cookie_same_site(SameSite::Strict) 
	.cookie_content_security(CookieContentSecurity::Private) // encrypt
	.cookie_http_only(true) // disallow scripts from reading
	.build()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    actix_web::HttpServer::new( || {
	actix_web::App::new()
	    .wrap(session_middleware())
	    .service(index)
    })
    .bind(("127.0.0.1", 3000))?
    .run()
    .await
}
```

### Reading from a cookie in Actix Web

Reading from and writing to cookies is very simple with the `Session`extractor. Just include a parameter typed as `Session`, and the framework will take care of the rest.

```rust
use actix_session::{ SessionMiddleware, Session };
use actix_session::config::{ BrowserSession, CookieContentSecurity };
use actix_session::storage::{ CookieSessionStore };
use actix_web::{ HttpResponse };
use actix_web::cookie::{ Key, SameSite };
use actix_web::web::{ Json };

#[actix_web::get("get_session")]
async fn get_session(session: Session) -> impl actix_web::Responder {
    match session.get::<String>("message") {
	Ok(message_option) => {
	    match message_option {
		Some(message) => HttpResponse::Ok().body(message),
		None => HttpResponse::NotFound().body("Not set.")
	    }
	}
	Err(_) => HttpResponse::InternalServerError().body("Session error.")
    }
}

#[actix_web::get("/")]
async fn index() -> impl actix_web::Responder {
    HttpResponse::Ok().body("Hello, world")
}

fn session_middleware() -> SessionMiddleware<CookieSessionStore> {
    SessionMiddleware::builder(
        CookieSessionStore::default(), Key::from(&[0; 64])
    )
	.cookie_name(String::from("my-kata-cookie"))
	.cookie_secure(true)
	.session_lifecycle(BrowserSession::default())
	.cookie_same_site(SameSite::Strict)
	.cookie_content_security(CookieContentSecurity::Private)
	.cookie_http_only(true)
	.build()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    actix_web::HttpServer::new( || {
	actix_web::App::new()
	    .wrap(session_middleware())
	    .service(index)
	    .service(get_session)
    })
    .bind(("127.0.0.1", 3000))?
    .run()
    .await
}
```

### Setting the session cookie in Actix Web

We can use `serde` to create an endpoint to set the session. We'll also need the `derive` feature.

```bash
cargo add serde --features derive
```

At this point, your _Cargo.toml_ should look like this:

```toml
[package]
name = "actix-kata"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-session = { version = "0.7.2", features = ["cookie-session"] }
actix-web = "4.3.1"
serde = { version = "1.0.164", features = ["derive"] }
```

With `serde`, we can make a deserializable `struct` to model our input and then extract it simply by putting it in the signature of our method. The framework will handle the rest.

Then we can insert the message using `session.insert`.

_[main.rs](http://main.rs/)_

```rust
use actix_session::{ SessionMiddleware, Session };
use actix_session::config::{ BrowserSession, CookieContentSecurity };
use actix_session::storage::{ CookieSessionStore };
use actix_web::{ HttpResponse };
use actix_web::cookie::{ Key, SameSite };
use actix_web::web::{ Json };
use serde;

#[derive(serde::Deserialize)]
struct CookieModel {
    message: String
}

#[actix_web::get("get_session")]
async fn get_session(session: Session) -> impl actix_web::Responder {
    match session.get::<String>("message") {
	Ok(message_option) => {
	    match message_option {
		Some(message) => HttpResponse::Ok().body(message),
		None => HttpResponse::NotFound().body("Not set.")
	    }
	}
	Err(_) => HttpResponse::InternalServerError().body("Error.")
    }
}

#[actix_web::get("/")]
async fn index() -> impl actix_web::Responder {
    HttpResponse::Ok().body("Hello, world")
}

async fn set_session(session: Session, model: Json<CookieModel>)
                     -> impl actix_web::Responder
{
    match session.insert("message", model.message.clone()) {
        Ok(_) => HttpResponse::Created().body("Created."),
        Err(_) => HttpResponse::InternalServerError().body("Error.")
    }
}

fn session_middleware() -> SessionMiddleware<CookieSessionStore> {
    SessionMiddleware::builder(
        CookieSessionStore::default(), Key::from(&[0; 64])
    )
	.cookie_name(String::from("my-kata-cookie"))
	.cookie_secure(true)
	.session_lifecycle(BrowserSession::default())
	.cookie_same_site(SameSite::Strict)
	.cookie_content_security(CookieContentSecurity::Private)
	.cookie_http_only(true)
	.build()
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    actix_web::HttpServer::new( || {
	actix_web::App::new()
	    .wrap(session_middleware())
	    .service(index)
	    .service(get_session)
        .route("set_session", actix_web::web::post().to(set_session))
    })
    .bind(("127.0.0.1", 3000))?
    .run()
    .await
}
```

### Wrapping up

And that's really it. Make sure you set your mime type for the `/set_session` request to `application/json` if you're testing. This has been helpful to me as I get into Actix Web middleware for the first time, so I hope it has been helpful to you.