Debug School

rakesh kumar
rakesh kumar

Posted on

Session Timeout & Auto Logout in Actix-Web (Rust)

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

How It Works

Store last_activity timestamp in session

On every request, check time difference

If expired → logout
Enter fullscreen mode Exit fullscreen mode

Step 1: Store Activity Time During Login

use chrono::Utc;

session.insert("last_activity", Utc::now().timestamp())?;
Enter fullscreen mode Exit fullscreen mode

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 })
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Add Middleware in main.rs

.wrap(SessionTimeout)
Enter fullscreen mode Exit fullscreen mode
.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)),
        )
)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)