Actix Identity
Library
Actix Identity
Overview
Actix Identity is a specialized authentication and session management library for the high-performance Rust web framework Actix Web. As of 2025, it has gained significant attention alongside the growth of the Rust ecosystem, characterized by memory safety and zero-cost abstraction for performance optimization. It provides cookie-based authentication, session management, and user identification features in a lightweight and simple manner, improving the developer experience through tight integration with Actix Web. It is ideal for microservices, APIs, and real-time web applications, achieving overwhelming processing performance compared to other web frameworks. However, attention is needed for Rust's learning cost and limited ecosystem.
Details
Actix Identity 2025 is fully integrated with the Actix Web middleware system, minimizing request processing overhead. It supports cookie-based authentication (Signed/Encrypted), session storage (memory, Redis, database), CSRF protection, and secure cookie settings as standard features. It enables building robust web applications through asynchronous processing, error handling, and type safety. It also includes modern security features such as TLS/HTTPS support, SameSite attribute settings, and cookie expiration management, meeting production environment requirements.
Key Features
- Complete Actix Web Integration: Performance optimized as native middleware
- Type-safe Session Management: Robust API design through Rust's type system
- Flexible Storage: Support for multiple backends including memory, Redis, PostgreSQL
- Secure Cookies: Complete support for encryption, signing, and HTTPSOnly
- Lightweight Design: Minimal dependencies and zero-cost abstraction
- Async Support: High concurrency processing with Tokio runtime
Pros and Cons
Pros
- Overwhelming performance with significant reduction in memory usage and CPU utilization
- Prevents bugs at compile time through Rust's type system, eliminating runtime errors
- High development efficiency and low learning cost through optimized integration with Actix Web
- Maintains high throughput in production environments through zero-cost abstraction
- Memory safety guarantees fundamentally avoid security risks
- Lightweight design minimizes dependencies, reducing build time and deployment size
Cons
- High learning cost as Rust proficiency is required, even for developers with other language experience
- No portability when changing frameworks as it's exclusive to Actix Web
- Limited ecosystem with constraints on integration with other authentication libraries
- Limited debugging and troubleshooting information and tools
- Enterprise features (SAML, LDAP, OAuth2) require additional implementation
- Organizational cost of Rust skill acquisition across the entire development team
Reference Pages
Code Examples
Basic Setup
# Cargo.toml
[dependencies]
actix-web = "4.4"
actix-identity = "0.6"
actix-session = "0.8"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
env_logger = "0.10"
Basic Cookie Authentication Implementation
// main.rs
use actix_web::{web, App, HttpServer, Result, HttpResponse, HttpRequest};
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
use actix_web::cookie::Key;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
#[derive(Serialize)]
struct UserInfo {
username: String,
role: String,
}
// Login processing
async fn login(
form: web::Json<LoginForm>,
identity: Identity,
) -> Result<HttpResponse> {
// In actual applications, perform database verification
if form.username == "admin" && form.password == "password123" {
// Set user ID (save to session)
identity.remember(form.username.clone())?;
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "Login successful",
"username": form.username
})))
} else {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Authentication failed"
})))
}
}
// Logout processing
async fn logout(identity: Identity) -> Result<HttpResponse> {
identity.forget(); // Remove from session
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "Logged out"
})))
}
// Protected endpoint requiring authentication
async fn protected(
identity: Option<Identity>,
) -> Result<HttpResponse> {
match identity {
Some(user) => {
if let Some(user_id) = user.id().ok() {
Ok(HttpResponse::Ok().json(UserInfo {
username: user_id,
role: "user".to_string(),
}))
} else {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Authentication required"
})))
}
}
None => {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Authentication required"
})))
}
}
}
// Public endpoint
async fn public() -> Result<HttpResponse> {
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "This endpoint is accessible to everyone",
"timestamp": chrono::Utc::now().to_rfc3339()
})))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
// Secret key for sessions (get from environment variables in production)
let secret_key = Key::generate();
HttpServer::new(move || {
App::new()
// Session middleware configuration
.wrap(
SessionMiddleware::builder(
CookieSessionStore::default(),
secret_key.clone()
)
.cookie_secure(false) // For development (true in production)
.cookie_http_only(true)
.cookie_same_site(actix_web::cookie::SameSite::Lax)
.cookie_max_age(actix_web::cookie::time::Duration::hours(24))
.build()
)
// Identity middleware configuration
.wrap(IdentityMiddleware::default())
// Route configuration
.route("/login", web::post().to(login))
.route("/logout", web::post().to(logout))
.route("/protected", web::get().to(protected))
.route("/public", web::get().to(public))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Advanced Configuration with Redis Session Storage
// advanced_session.rs
use actix_web::{web, App, HttpServer, Result, HttpResponse, middleware::Logger};
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::{SessionMiddleware, storage::RedisSessionStore};
use actix_web::cookie::Key;
use redis::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone)]
struct User {
id: u32,
username: String,
email: String,
role: String,
permissions: Vec<String>,
last_login: String,
}
#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
remember_me: Option<bool>,
}
// In-memory user database (use PostgreSQL/MySQL in production)
static mut USERS: Option<HashMap<String, User>> = None;
fn init_users() {
let mut users = HashMap::new();
users.insert("admin".to_string(), User {
id: 1,
username: "admin".to_string(),
email: "[email protected]".to_string(),
role: "administrator".to_string(),
permissions: vec!["read".to_string(), "write".to_string(), "delete".to_string()],
last_login: chrono::Utc::now().to_rfc3339(),
});
users.insert("user".to_string(), User {
id: 2,
username: "user".to_string(),
email: "[email protected]".to_string(),
role: "user".to_string(),
permissions: vec!["read".to_string()],
last_login: chrono::Utc::now().to_rfc3339(),
});
unsafe {
USERS = Some(users);
}
}
// Advanced login processing
async fn advanced_login(
form: web::Json<LoginRequest>,
identity: Identity,
req: actix_web::HttpRequest,
) -> Result<HttpResponse> {
let users = unsafe { USERS.as_ref().unwrap() };
if let Some(user) = users.get(&form.username) {
// Password verification (compare with hashed password in production)
if verify_password(&form.password, &user.username) {
// Session duration configuration
let session_duration = if form.remember_me.unwrap_or(false) {
30 * 24 * 60 * 60 // 30 days
} else {
8 * 60 * 60 // 8 hours
};
// Save user information as JSON
let user_data = serde_json::to_string(user).unwrap();
identity.remember(user_data)?;
// Log login information
log::info!(
"User {} logged in from IP: {}",
user.username,
req.connection_info().realip_remote_addr().unwrap_or("unknown")
);
Ok(HttpResponse::Ok().json(serde_json::json!({
"message": "Login successful",
"user": {
"username": user.username,
"role": user.role,
"permissions": user.permissions
},
"session_duration": session_duration
})))
} else {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Incorrect password"
})))
}
} else {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "User not found"
})))
}
}
// Password verification (simplified version)
fn verify_password(password: &str, username: &str) -> bool {
// Use bcrypt etc. for hashed password comparison in production
match username {
"admin" => password == "admin123",
"user" => password == "user123",
_ => false,
}
}
// Get user information
async fn get_user_info(identity: Option<Identity>) -> Result<HttpResponse> {
match identity {
Some(user_identity) => {
if let Ok(user_data) = user_identity.id() {
if let Ok(user) = serde_json::from_str::<User>(&user_data) {
Ok(HttpResponse::Ok().json(user))
} else {
Ok(HttpResponse::InternalServerError().json(serde_json::json!({
"error": "Failed to parse user data"
})))
}
} else {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Authentication required"
})))
}
}
None => {
Ok(HttpResponse::Unauthorized().json(serde_json::json!({
"error": "Authentication required"
})))
}
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
init_users();
// Redis connection configuration
let redis_store = RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.expect("Failed to connect to Redis");
let secret_key = Key::from(
std::env::var("SECRET_KEY")
.unwrap_or_else(|_| "your-secret-key-here-32-chars-long".to_string())
.as_bytes()
);
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.wrap(
SessionMiddleware::builder(
redis_store.clone(),
secret_key.clone()
)
.cookie_name("actix-session")
.cookie_secure(true) // For production environment
.cookie_http_only(true)
.cookie_same_site(actix_web::cookie::SameSite::Strict)
.cookie_max_age(actix_web::cookie::time::Duration::hours(8))
.session_lifecycle(
actix_session::config::PersistentSession::default()
.session_ttl(actix_web::cookie::time::Duration::hours(8))
)
.build()
)
.wrap(IdentityMiddleware::default())
.route("/login", web::post().to(advanced_login))
.route("/logout", web::post().to(logout))
.route("/user", web::get().to(get_user_info))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Security and Authorization Implementation
// security.rs
use actix_web::{dev::ServiceRequest, Error, HttpMessage};
use actix_identity::Identity;
use serde::{Deserialize, Serialize};
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_web::dev::{Service, ServiceResponse, Transform};
use futures_util::future::{ok, Ready};
#[derive(Serialize, Deserialize, Clone)]
struct UserContext {
id: u32,
username: String,
role: String,
permissions: Vec<String>,
}
// Authorization middleware
pub struct AuthorizationMiddleware {
required_permission: String,
}
impl AuthorizationMiddleware {
pub fn new(permission: String) -> Self {
Self {
required_permission: permission,
}
}
}
// CSRF protection
use actix_web::cookie::Cookie;
use rand::Rng;
pub fn generate_csrf_token() -> String {
let mut rng = rand::thread_rng();
(0..32)
.map(|_| rng.sample(rand::distributions::Alphanumeric) as char)
.collect()
}
pub async fn get_csrf_token(
req: actix_web::HttpRequest,
) -> Result<actix_web::HttpResponse, Error> {
let token = generate_csrf_token();
let cookie = Cookie::build("csrf_token", token.clone())
.http_only(true)
.secure(true)
.same_site(actix_web::cookie::SameSite::Strict)
.finish();
Ok(actix_web::HttpResponse::Ok()
.cookie(cookie)
.json(serde_json::json!({
"csrf_token": token
})))
}
Integration Testing and Error Handling
// tests.rs
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{test, App};
use actix_session::{SessionMiddleware, storage::CookieSessionStore};
use actix_identity::IdentityMiddleware;
use actix_web::cookie::Key;
#[actix_web::test]
async fn test_login_success() {
let secret_key = Key::generate();
let app = test::init_service(
App::new()
.wrap(
SessionMiddleware::builder(
CookieSessionStore::default(),
secret_key.clone()
)
.build()
)
.wrap(IdentityMiddleware::default())
.route("/login", web::post().to(login))
).await;
let login_data = LoginForm {
username: "admin".to_string(),
password: "password123".to_string(),
};
let req = test::TestRequest::post()
.uri("/login")
.set_json(&login_data)
.to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
#[actix_web::test]
async fn test_protected_endpoint_unauthorized() {
let secret_key = Key::generate();
let app = test::init_service(
App::new()
.wrap(
SessionMiddleware::builder(
CookieSessionStore::default(),
secret_key.clone()
)
.build()
)
.wrap(IdentityMiddleware::default())
.route("/protected", web::get().to(protected))
).await;
let req = test::TestRequest::get()
.uri("/protected")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), 401);
}
}
// Error handling
use actix_web::{ResponseError, HttpResponse};
use std::fmt;
#[derive(Debug)]
enum AuthError {
InvalidCredentials,
SessionExpired,
InsufficientPermissions,
InternalError(String),
}
impl fmt::Display for AuthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthError::InvalidCredentials => write!(f, "Invalid credentials"),
AuthError::SessionExpired => write!(f, "Session expired"),
AuthError::InsufficientPermissions => write!(f, "Insufficient permissions"),
AuthError::InternalError(msg) => write!(f, "Internal error: {}", msg),
}
}
}
impl ResponseError for AuthError {
fn error_response(&self) -> HttpResponse {
match self {
AuthError::InvalidCredentials => {
HttpResponse::Unauthorized().json(serde_json::json!({
"error": "invalid_credentials",
"message": "Invalid credentials"
}))
}
AuthError::SessionExpired => {
HttpResponse::Unauthorized().json(serde_json::json!({
"error": "session_expired",
"message": "Session expired"
}))
}
AuthError::InsufficientPermissions => {
HttpResponse::Forbidden().json(serde_json::json!({
"error": "insufficient_permissions",
"message": "Insufficient permissions"
}))
}
AuthError::InternalError(_) => {
HttpResponse::InternalServerError().json(serde_json::json!({
"error": "internal_error",
"message": "An internal error occurred"
}))
}
}
}
}