Logout For Inactive User Does Not Work In Prod Environment: React Front-end, Spring Back-end

31 views Asked by At

We are using Okta for authentication. When an application is deployed on the CentOS OS blade, the inactive user is logged out and immediately logged back in. When testing the application on the localhost, this behavior can be reproduced with a timeout of less than 60 seconds. Timeouts greater than 60 seconds resulted in a successful logout. I was rather perplexed when I realized that after 5 minutes on the server, the user had been logging out and logging in. Extremely disturbing, since that does not happen on the localhost. Here are App.js and IdleTimer implementations:

export default function App() {
    const [isTimeout,setIsTimeout] = useState(false);

    useEffect(() => {
        const timer = new IdleTimer({
            timeOutInSeconds: 500,
            onTimeout() {
                setIsTimeout(true);
            },
            onExpired() {
                setIsTimeout(true);
            }
        });
        return () => {
            timer.logout();
            timer.cleanUp();
        }
    }, [isTimeout]);

    if(isTimeout) {
        console.log("logging out due to inactivity");
    } else {
    return (
        <CookiesProvider>
            <Router>
                <div>
                    <Switch>

                    </Switch>
                    <FooterBar/>
                </div>
            </Router>
        </CookiesProvider>
    )}
}

//Our IdleTimer Implementation

class IdleTimer {


    #timeInterval = 5000;
    #setTimeOut = 300;
    #fractionInSeconds = 1000;
    #parseRadix = 10
    constructor({ timeOutInSeconds, clearCookie = false, onTimeout, onExpired}) {
        this.timeout = timeOutInSeconds;
        this.onTimeout = onTimeout;
        this.clearCookie = clearCookie;
        const expiredTime = parseInt(sessionStorage.getItem("_expiredTime") || 0, this.#parseRadix);
        if (expiredTime > 0 && expiredTime < Date.now()) {
            onExpired();
            return;
        }
        this.eventHandler = this.updateExpiredTime.bind(this);
        this.tracker();
        this.startInterval();
    }
    startInterval(){
        this.updateExpiredTime();
        this.interval = setInterval(()=> {
            const expiredTime = parseInt(sessionStorage.getItem("_expiredTime") || 0, this.#parseRadix);
            if (expiredTime < Date.now()) {
                console.log("Clean Cookies: ", expiredTime+ ' = '+ Date.now())
                if (this.onTimeout) {
                    this.onTimeout();
                }
                this.cleanCookies();
                this.cleanUp();
                this.logout();
            }
        }, this.#timeInterval)
    }
    updateExpiredTime() {
        if (this.timeoutTracker) {
            clearTimeout(this.timeoutTracker);
        }
        this.timeoutTracker = setTimeout(() => {
            sessionStorage.setItem("_expiredTime", Date.now() + this.timeout * this.#fractionInSeconds);
        }, this.#setTimeOut);
    }

    tracker() {
        window.addEventListener("mousemove", this.eventHandler);
        window.addEventListener("scroll", this.eventHandler);
        window.addEventListener("keydown", this.eventHandler);
    }

   cleanUp() {
        sessionStorage.removeItem("_expiredTime");
        clearInterval(this.interval);
        window.removeEventListener("mousemove", this.eventHandler);
        window.removeEventListener("scroll", this.eventHandler);
        window.removeEventListener("keydown", this.eventHandler);
        window.location.reload();
        window.location.href = "/";
    }

    cleanCookies(){
        if(this.clearCookie) {
            var cookies = cookies.keys();
            for (var index in cookies) {
                cookies.remove(cookies[index]);
            }
        }
    }
    logout = () => {
        const [cookie, setCookie] = useCookies('XSRF-TOKEN');
        console.log("logout");
        fetch('/application/logout', {
            method: 'POST', credentials: 'include',
            headers: {'X-XSRF-TOKEN': cookie}
        }).then(res => res.json())
            .then(response => {
                console.log(response);
                window.location.href = response.logoutUrl + "?id_token_hint=" +
                    response.idToken + "&post_logout_redirect_uri=" + window.location.origin;
            });
    }
}
export default IdleTimer;

I was able to reproduce logging in and logging out by setting a timeout of less than 60 seconds. What it looks like is happening is that the response from a logout call I not coming back soon enough, but it is very deceptive, since by a miracle, it works correctly at the 60-second mark.

1

There are 1 answers

0
Bad Vlad On

So the real problem was that in the header of the logout request needs to have a specific cookie and not a whole cookie jar. This change did the fix:

headers: {'X-XSRF-TOKEN': cookie['XSRF-TOKEN']}

What made this problem difficult to solve was that the idle time logout would work under the full build on the localhost and then fail in the production. I was only able to reproduce behavior from the production once when I used timeout of less than 30 seconds.