Introduction
Session timeout automatically logs out users after inactivity. This prevents attackers from using an open browser session.
Used in:
Banking systems
Admin dashboards
Enterprise applications
Why Session Timeout is Important
If a user leaves their system open:
Anyone can access their dashboard
Sensitive data can be stolen
Timeout adds an extra layer of protection.
How It Works
Store last_activity timestamp in session
On every request, check time difference
If expired → logout
Step 1: Store Activity Time During Login
use chrono::Utc;
session.insert("last_activity", Utc::now().timestamp())?;
Step 2: Create Middleware to Check Timeout
Create file:
src/middleware/session_timeout.rs
use actix_web::{dev::{Service, ServiceRequest, ServiceResponse}, Error, error};
use actix_service::{Transform};
use futures_util::future::{ready, LocalBoxFuture, Ready};
use chrono::Utc;
use actix_session::SessionExt;
pub struct SessionTimeout;
impl<S, B> Transform<S, ServiceRequest> for SessionTimeout
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = SessionTimeoutMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SessionTimeoutMiddleware { service }))
}
}
pub struct SessionTimeoutMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SessionTimeoutMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn call(&self, req: ServiceRequest) -> Self::Future {
let session = req.get_session();
let now = Utc::now().timestamp();
if let Ok(Some(last)) = session.get::<i64>("last_activity") {
if now - last > 1800 {
session.purge();
return Box::pin(async { Err(error::ErrorUnauthorized("Session expired")) });
}
}
let _ = session.insert("last_activity", now);
let fut = self.service.call(req);
Box::pin(async move { fut.await })
}
}
Step 3: Add Middleware in main.rs
.wrap(SessionTimeout)
.service(
web::scope("/api")
// ✅ Public routes (NO timeout)
.route("/login", web::post().to(handlers::login))
.route("/logout", web::post().to(handlers::logout))
// ✅ Protected routes (WITH timeout)
.service(
web::scope("")
.wrap(middleware::session_timeout::SessionTimeout::new(1800)) // 30 min
.route("/me", web::get().to(handlers::me))
.route("/dashboard", web::get().to(handlers::dashboard))
.route("/shops", web::get().to(handlers::list_shops_handler))
.route("/shops", web::post().to(handlers::create_shop_handler))
.route("/shops/{id}", web::get().to(handlers::get_shop_handler))
.route("/shops/{id}", web::put().to(handlers::update_shop_handler))
.route("/shops/{id}", web::delete().to(handlers::delete_shop_handler))
.route("/vehicles", web::get().to(handlers::list_vehicles_handler))
.route("/home/vehicles", web::get().to(handlers::list_home_vehicle_cards_handler))
.route("/vehicles", web::post().to(handlers::create_vehicle_handler))
.route("/vehicles/upload-documents", web::post().to(handlers::upload_vehicle_documents_handler))
.route("/vehicles/upload-partner-image", web::post().to(handlers::upload_vehicle_partner_image_handler))
.route("/vehicles/{id}", web::get().to(handlers::get_vehicle_handler))
.route("/vehicles/{id}", web::put().to(handlers::update_vehicle_handler))
.route("/vehicles/{id}", web::delete().to(handlers::delete_vehicle_handler))
.route("/bookings/offline", web::post().to(handlers::save_offline_booking_handler))
.route("/brand-models", web::get().to(handlers::list_brand_models_handler))
.route("/brand-models", web::post().to(handlers::create_brand_model_handler))
.route("/brand-models/upload-images", web::post().to(handlers::upload_brand_model_images_handler))
.route("/brand-models/upload-images/", web::post().to(handlers::upload_brand_model_images_handler))
.route("/brand-model/upload-images", web::post().to(handlers::upload_brand_model_images_handler))
.route("/brand-models/{id}", web::get().to(handlers::get_brand_model_handler))
.route("/brand-models/{id}", web::put().to(handlers::update_brand_model_handler))
.route("/brand-models/{id}", web::delete().to(handlers::delete_brand_model_handler)),
)
)
Top comments (0)