Session Authentication
Traditional session-based authentication using cookies.
Overview
Session authentication is ideal for:
- Traditional web applications
- Server-rendered pages
- Applications requiring logout/session invalidation
Feature Flag
Enable session support in your Cargo.toml:
[dependencies]
actix-security = { version = "0.2", features = ["session"] }
actix-session = { version = "0.10", features = ["cookie-session"] }
Quick Start
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
use actix_session::config::CookieContentSecurity;
use actix_web::cookie::Key;
use actix_security::http::security::session::{SessionAuthenticator, SessionConfig};
use actix_security::http::security::middleware::SecurityTransform;
// Generate a secure key (in production, load from environment)
let secret_key = Key::generate();
// Session middleware
let session_middleware = SessionMiddleware::builder(
CookieSessionStore::default(),
secret_key.clone()
)
.cookie_secure(true) // HTTPS only in production
.cookie_content_security(CookieContentSecurity::Private)
.build();
// Session authenticator
let session_config = SessionConfig::new();
let authenticator = SessionAuthenticator::new(session_config.clone());
App::new()
.wrap(session_middleware)
.wrap(
SecurityTransform::new()
.config_authenticator(move || authenticator.clone())
.config_authorizer(|| AuthorizationManager::request_matcher())
)
.app_data(web::Data::new(session_config))
Configuration
let config = SessionConfig::new()
// Custom session key for user data (default: "security_user")
.user_key("my_user")
// Custom session key for auth flag (default: "security_authenticated")
.authenticated_key("my_auth");
Login/Logout
Login Handler
use actix_session::Session;
use actix_security::http::security::session::{SessionAuthenticator, SessionConfig};
use actix_security::http::security::User;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
#[post("/login")]
async fn login(
session: Session,
form: web::Form<LoginForm>,
config: web::Data<SessionConfig>,
users: web::Data<MemoryAuthenticator>,
encoder: web::Data<Argon2PasswordEncoder>,
) -> impl Responder {
// Find user
let user = match users.find_user(&form.username) {
Some(u) => u,
None => return HttpResponse::Unauthorized().body("Invalid credentials"),
};
// Verify password
if !encoder.matches(&form.password, user.get_password()) {
return HttpResponse::Unauthorized().body("Invalid credentials");
}
// Store user in session
match SessionAuthenticator::login(&session, &user, &config) {
Ok(_) => HttpResponse::Ok().body(format!("Welcome, {}!", user.get_username())),
Err(e) => HttpResponse::InternalServerError().body(format!("Login failed: {}", e)),
}
}
Logout Handler
#[post("/logout")]
async fn logout(session: Session, config: web::Data<SessionConfig>) -> impl Responder {
SessionAuthenticator::logout(&session, &config);
HttpResponse::Ok().body("Logged out")
}
// Or clear entire session
#[post("/logout/all")]
async fn logout_all(session: Session) -> impl Responder {
SessionAuthenticator::clear_session(&session);
HttpResponse::Ok().body("Session cleared")
}
Session Utilities
Check Authentication
#[get("/status")]
async fn auth_status(session: Session, config: web::Data<SessionConfig>) -> impl Responder {
if SessionAuthenticator::is_authenticated(&session, &config) {
let user = SessionAuthenticator::get_session_user(&session, &config);
HttpResponse::Ok().json(serde_json::json!({
"authenticated": true,
"username": user.map(|u| u.get_username().to_string())
}))
} else {
HttpResponse::Ok().json(serde_json::json!({
"authenticated": false
}))
}
}
Complete Example
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use actix_session::{Session, SessionMiddleware, storage::CookieSessionStore};
use actix_web::cookie::Key;
use actix_security::secured;
use actix_security::http::security::{
AuthenticatedUser, AuthenticationManager, AuthorizationManager,
Argon2PasswordEncoder, PasswordEncoder, User,
};
use actix_security::http::security::session::{SessionAuthenticator, SessionConfig};
use actix_security::http::security::middleware::SecurityTransform;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Welcome! Please login at /login")
}
#[get("/login")]
async fn login_page() -> impl Responder {
HttpResponse::Ok()
.content_type("text/html")
.body(r#"
<form method="post" action="/login">
<input name="username" placeholder="Username">
<input name="password" type="password" placeholder="Password">
<button type="submit">Login</button>
</form>
"#)
}
#[post("/login")]
async fn do_login(
session: Session,
form: web::Form<LoginForm>,
config: web::Data<SessionConfig>,
) -> impl Responder {
// In real app, validate against database
if form.username == "admin" && form.password == "admin" {
let user = User::new("admin".to_string(), "".to_string())
.roles(&["ADMIN".into(), "USER".into()]);
SessionAuthenticator::login(&session, &user, &config).unwrap();
HttpResponse::Found()
.insert_header(("Location", "/dashboard"))
.finish()
} else {
HttpResponse::Unauthorized().body("Invalid credentials")
}
}
#[post("/logout")]
async fn logout(session: Session, config: web::Data<SessionConfig>) -> impl Responder {
SessionAuthenticator::logout(&session, &config);
HttpResponse::Found()
.insert_header(("Location", "/"))
.finish()
}
#[secured("USER")]
#[get("/dashboard")]
async fn dashboard(user: AuthenticatedUser) -> impl Responder {
HttpResponse::Ok().body(format!("Welcome to dashboard, {}!", user.get_username()))
}
#[secured("ADMIN")]
#[get("/admin")]
async fn admin(user: AuthenticatedUser) -> impl Responder {
HttpResponse::Ok().body(format!("Admin panel for {}", user.get_username()))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let secret_key = Key::generate();
let session_config = SessionConfig::new();
HttpServer::new(move || {
let config = session_config.clone();
let authenticator = SessionAuthenticator::new(config.clone());
App::new()
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
.build()
)
.wrap(
SecurityTransform::new()
.config_authenticator(move || authenticator.clone())
.config_authorizer(|| {
AuthorizationManager::request_matcher()
.login_url("/login")
})
)
.app_data(web::Data::new(config))
.service(index)
.service(login_page)
.service(do_login)
.service(logout)
.service(dashboard)
.service(admin)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Session Storage Options
Cookie Session (Default)
use actix_session::storage::CookieSessionStore;
SessionMiddleware::new(CookieSessionStore::default(), secret_key)
Redis Session
[dependencies]
actix-session = { version = "0.10", features = ["redis-session"] }
use actix_session::storage::RedisSessionStore;
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379").await?;
SessionMiddleware::new(redis_store, secret_key)
Security Best Practices
- Use secure cookies - Set
cookie_secure(true)in production - Use HTTP-only cookies - Prevents JavaScript access
- Set appropriate expiration - Balance security and UX
- Regenerate session on login - Prevent session fixation
- Use HTTPS - Always use HTTPS in production
- Implement CSRF protection - For form submissions
Spring Security Comparison
Spring Security:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard"))
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/"));
return http.build();
}
}
Actix Security:
// Session middleware handles cookie management
SessionMiddleware::new(CookieSessionStore::default(), secret_key)
// Login/logout handled in handlers
SessionAuthenticator::login(&session, &user, &config)?;
SessionAuthenticator::logout(&session, &config);