Skip to content

Instantly share code, notes, and snippets.

@EduVencovsky
Last active February 20, 2024 03:28
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save EduVencovsky/f8f6c275f42f7352571c92a59309e31d to your computer and use it in GitHub Desktop.
Save EduVencovsky/f8f6c275f42f7352571c92a59309e31d to your computer and use it in GitHub Desktop.
Private Routes with Auth using react-router and Context API
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { checkIsAuthenticated, authSignUp, authLogin, authLogout } from '../../services/auth'
export const AuthContext = React.createContext({})
export default function Auth({ children }) {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
checkAuth()
}, [])
const checkAuth = () => checkIsAuthenticated()
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false))
.then(() => setIsLoading(false))
const login = credentials => authLogin(credentials)
.then(setIsAuthenticated(true))
.catch(error => {
alert(error)
setIsAuthenticated(false)
})
const logout = () => {
authLogout()
setIsAuthenticated(false)
}
const signUp = credentials => authSignUp(credentials)
.then(setIsAuthenticated(true))
.catch(error => {
alert(error)
setIsAuthenticated(false)
})
return (
<AuthContext.Provider value={{ isAuthenticated, isLoading, login, logout, signUp }}>
{children}
</AuthContext.Provider>
)
}
Auth.propTypes = {
children: PropTypes.oneOfType([
PropTypes.func,
PropTypes.array
])
}
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import PrivateRoute from './components/PrivateRoute/PrivateRoute'
import Auth from './components/Auth/Auth'
import Header from './components/Header/Header'
import HomePage from './views/HomePage/HomePage'
import SignUp from './views/SignUp/SignUp'
import SignIn from './views/SignIn/SignIn'
import FormList from './views/FormList/FormList'
import PageNotFound from './views/PageNotFound/PageNotFound'
export default function App() {
return (
<div>
<Auth>
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/signup" component={SignUp} />
<Route path="/signin" component={SignIn} />
<PrivateRoute path="/forms" component={FormList} />
<Route component={PageNotFound} />
</Switch>
</Auth>
</div>
)
}
import React, { useContext } from 'react'
import { Route, Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { AuthContext } from '../Auth/Auth'
import Loading from '../../views/Loading/Loading'
const PrivateRoute = ({ component: Component, ...otherProps }) => {
const { isAuthenticated, isLoading } = useContext(AuthContext)
return (
<Route
{...otherProps}
render={props => (
!isLoading
?
(
isAuthenticated
?
<Component {...props} />
:
<Redirect to={otherProps.redirectTo ? otherProps.redirectTo : '/signin'} />
)
:
<Loading />
)}
/>
)
}
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired
}
export default PrivateRoute
@hvolschenk
Copy link

Thanks for the example.
I have a question though, something I have not been able to figure out.

When any API call fails with a 401 from the Backend, I would like to also log the user out.
I can only see two solutions here, both of which are truly not ideal:

Wrap all API calls in Context

I could wrap all API calls in their own Context Provider and get all async methods from context,
this way if any API call fails with a 401 I can update the AuthContext and set the user as logged out.

Wrapping all API calls in Context feels a bit strange though as that might be a quite large object
being passed around instead of just importing the one(s) I need directly.

Handle 401s in each component

There is also the possibility of duplicating the 401 code in each Component making an API call.
Obviously this is not great as I could easily miss one, and this will introduce a lot of duplicated code.


Maybe you (or someone reading this) knows something I don't and can point me in the right direction?

@EduVencovsky
Copy link
Author

@hvolschenk This gist is kind of old and I made it far time ago, now what I recommend is having 2 contexts, one for checking if the user is authenticated and another to hold all the user informations. So when you call login, it will automatically set user credentials in a use context. After that, you can just use hooks go get the user info anytime you want.

@HDaghash
Copy link

like this approach, thanks

@HDaghash
Copy link

HDaghash commented May 26, 2020

@hvolschenk This gist is kind of old and I made it far time ago, now what I recommend is having 2 contexts, one for checking if the user is authenticated and another to hold all the user informations. So when you call login, it will automatically set user credentials in a use context. After that, you can just use hooks go get the user info anytime you want.

is there a way to trigger checkAuth on route change this context will keep using same isAuthenticated flag first value all the time till you refresh

@HDaghash
Copy link

Hey @hvolschenk by any chance did you find a solution for the mentioned concerns? regarding the 401 errors

@EduVencovsky
Copy link
Author

is there a way to trigger checkAuth on route change this context will keep using same isAuthenticated flag first value all the time till you refresh

Yes you can, but there is alot of ways of doing this. You want on every route change to call checkAuth? Any Route should do this or only Private routes? Does nested routes count on this?

@HDaghash what you are asking is something too much specific for this generalized gist, because it will depend on your application.

But if you want to implement it, you could pass checkAuth to the context value and call it when you need it. (It will be better if you create a custom hook that does that)

@EduVencovsky
Copy link
Author

When any API call fails with a 401 from the Backend, I would like to also log the user out.

What do you mean by "log the user out"? Now reading you question again I can see that if you want, you can add some logic in checkAuth's .then or .catch that will check for 401 status and call logout

@HDaghash
Copy link

@EduVencovsky thank you for your response, I meant return user back to the sign-in page and wipe the current token, now I solve it by dispatching an action by redux to do that since the Hook Context will not be available everywhere for example (inside Axios interceptor) when I catch the 401 error of any request.

Regarding the checking on each private route, I think this is should be a generic case since we don't want to a user browsing the private pages while his token has been expired, that's why we need each page change for a private route to check if the token still valid that's part I didn't figure it out yet

@hvolschenk
Copy link

@EduVencovsky thank you for your response, I meant return user back to the sign-in page and wipe the current token, now I solve it by dispatching an action by redux to do that since the Hook Context will not be available everywhere for example (inside Axios interceptor) when I catch the 401 error of any request.

Yeah I am doing exactly the same. I use redux solely for this purpose (Also using axios).
There is another way, by wrapping all API calls in context and making them available through a hook or such,
but I have been trying this and it feels rather messy to be honest.

@HDaghash
Copy link

@hvolschenk Aha I see and I agree with dispatching action it's a better way than having multiple hooks traversing all these data.

@farhanasif
Copy link

thanks for the approach, well done

@NabinOjha
Copy link

NabinOjha commented Sep 18, 2020

However, when we reload the protected route page the initial value of isAuthenticated is false and we are redirected to /signin directly ......But when isAuthenticated is set to true after some time it has no effect on the current route since we are already on '/signin'.......Correct me if i am wrong but this approach does not seem to be working for me since checking isAuthenticated is a time consuming task????
Thank You.

@iqbalnur32
Copy link

Thanks Sir

@OmanCoding
Copy link

Where is ../../services/auth file is?

@aacassandra
Copy link

awesome, thanks sir.

@DisasterNatsu
Copy link

Where is ../../services/auth file is?

Same i also want to see how those functions atre

@IDONTSUDO
Copy link

async await завезли пацаны

@jakerich1
Copy link

Exactly what I've been looking for, thanks!

@DisasterNatsu
Copy link

DisasterNatsu commented Oct 26, 2021 via email

@ravikant-pal
Copy link

However, when we reload the protected route page the initial value of isAuthenticated is false and we are redirected to /signin directly ......But when isAuthenticated is set to true after some time it has no effect on the current route since we are already on '/signin'.......Correct me if i am wrong but this approach does not seem to be working for me since checking isAuthenticated is a time consuming task???? Thank You.

I have the same issue @NabinOjha did you find any solution?
@EduVencovsky what's your thought on this?

@DisasterNatsu
Copy link

However, when we reload the protected route page the initial value of isAuthenticated is false and we are redirected to /signin directly ......But when isAuthenticated is set to true after some time it has no effect on the current route since we are already on '/signin'.......Correct me if i am wrong but this approach does not seem to be working for me since checking isAuthenticated is a time consuming task???? Thank You.

I have the same issue @NabinOjha did you find any solution? @EduVencovsky what's your thought on this?

https://medium.com/@dennisivy/creating-protected-routes-with-react-router-v6-2c4bbaf7bc1c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment