William Sheppard
William Sheppard

Reputation: 105

React: How to log user out if their cookie is expired?

Background: On an express backend, I am using express-session to assign user sessions. On a React front-end, I have in place functionality to prevent users from entering protected routes. There is one bothersome edge case though: If an authenticated user remains in an application long enough for their cookie to expire, then they will have free reign to visit all authenticated routes as a "ghost" aka no cookie and and an inaccurate state: this.state.isAuthenticated = true(Yes, they're state is technically "authenticated", because they never logged out or remounted the component preventing me to fetch my backend and perform a check, so that is why I refer to them as "ghosts")

Why is that? Well, everytime my App component mounts, I make sure that I check the state of the user. If they pass this authentication phase, then my backend will provide them with a session and I set authenticated state to be true. Because my app component never remounts, as the user traverses through my application, they will be able to visit the unauthorized routes as a "ghost".

Caveat: If they happen to refresh at any point or press the logout button, then my component will remount, and remove their access since they are not a legitmate user.

I hope I have framed my situation well. My question is: how can I remove a user if they're cookie expires?

Edit: I have a working solution in Protectedroute.js, but checkAuth() is called an infinite amount of times unless the user moves away from the page. I want to avoid Redux, because I just started React a week ago

ProtectedRoute.js

function ProtectedRoute({component: Component}, {...rest}) {
    return(
    <userContext.Consumer>
        {({isAuthenticated, checkAuth}) => {
            checkAuth()
            return  <Route {...rest} render = {(props) => isAuthenticated ? (<Component {...props}/>) : (<Redirect to ='/login' />)} />
        }}
    </userContext.Consumer>
    )
}

App.js

class App extends Component {
    constructor(props, context) {
        super(props, context)

        this.state = {
            isAuthenticated: false,
            isLoading: true
        }

        this.handleLoggedIn = this.handleLoggedIn.bind(this)
        this.handleLoggedOut = this.handleLoggedOut.bind(this)
        this.isAuthenticated = this.isAuthenticated.bind(this)
    }

    componentDidMount() {
        //Get and set currently logged in user
        //console.log('--componentDidMount--')
        this.isAuthenticated()
    }


    isAuthenticated() {
        const url = 'http://localhost:9000/api/auth'
        fetch(url, {
            method: 'GET',
            credentials: 'include',
            headers: {
                'Content-Type' : 'application/json'
            }
        })
        .then((response) => response.text())
        .then((data) => {
            //console.log(data)
            if (data === 'true') {
                console.log('--isAuthenticated--')
                this.setState({
                    isAuthenticated: true,
                    isLoading: false
                })
            } else {
                this.setState({
                    isAuthenticated: false,
                    isLoading: false
                })
            }
        })
        .catch((err) => {
            console.log('Error', err)
        })
    }

    handleLoggedIn() {
        console.log('--handleLoggedIn--')
        this.setState({
            isAuthenticated: true
        })
    }

    handleLoggedOut() {
        this.setState({
            isAuthenticated: false
        })

    }

    render() {
        const value = {
            loggedIn: this.handleLoggedIn,
            loggedOut: this.handleLoggedOut,
            isAuthenticated: this.state.isAuthenticated,
            checkAuth: this.isAuthenticated
        }

        if (this.state.isLoading) {
            return(<div>LOADING</div>)
        } else {
            return (
                <Router>
                    <userContext.Provider value = {value}>
                        <div>
                            <userContext.Consumer>
                                {(value) => (<Navigation isAuthenticated = {this.state.isAuthenticated} />)}
                            </userContext.Consumer>
                        </div>
                        <Switch>
                            <Route path = '/signup'>
                                <Signup />
                            </Route>
                            <Route path = '/login'>
                                <Login handleLoggedIn={this.handleLoggedIn}/>
                            </Route>
                            <Route path = '/feed'>
                                <Feed />
                            </Route>
                            <Protectedroute path = '/create-post' component={CreatePost} />
                        </Switch>
                    </userContext.Provider>
                </Router>
            )
        }
    }
}

Upvotes: 1

Views: 9394

Answers (2)

Ali Kleit
Ali Kleit

Reputation: 3649

well using setTimeout is the way to go to check the cookie's expiration time at n interval of time.

Another way if you don't want to use setInterval is that you may want to develop a way so that you are checking the cookie for every secure request and then check if that request status code returns 401 unauthorized returned by your server, then you may show a modal shows your session is expired, click here to login...

Upvotes: 1

gautamits
gautamits

Reputation: 1292

You can create a fetchWrapper, which will call all fetch APIs to your backend. Whenever you detect that cookie received from frontend is expired, send a 401 response code. Listen for 401 in fetchWrapper and reload the page to login route whenever 401 encounters. There might be multiple solutions to your problem, this is one of them.

Upvotes: 0

Related Questions