Handling isAuthenticated only in the state means the user will be unauthenticated every time he refreshes the page. That's not really user-friendly! :)
So instead, the Login page should store an access_token (coming from your backend) in the cookies or localStorage of the browser. An access_token proves the user is authenticated and also verifies his identity. You will usually pass this access_token to every next requests to your server, to check if this user is allowed to access the data he's requesting, or allowed to create, edit and delete the things he's trying to create, edit and delete.
Then you can check this access_token on every other pages as well and redirect the user to the Login page if he's not authenticated anymore.
A brief aside on the difference between access_token and refresh_token – this will help you understand the code bellow, but feel free to skip ahead if you are already familiar with it.
Your backend probably uses OAuth2, which is the most common authentication protocol nowadays. With OAuth2, your app makes a first request to the server containing the username and password of the user to authenticate. Once the user is authenticated, he receives 1) an access_token, which usually expires after an hour, and 2) a refresh_token, which expires after a very long time (hours, days). When the access_token expires, instead of asking the user for his username and password again, your app sends the refresh_token to the server to obtain a new access_token for this user.
A brief aside on the differences between cookies and localStorage – feel free to skip it too!
localStorage is the most recent technology between both. It's a simple key/value persistence system, which seems perfect to store the access_token and its value. But we also need to persist its date of expiration. We could store a second key/value pair named expires but it would be more logic to handle on our side.
On the other hand, cookies have a native expires property, which is exactly what we need! cookies are an old technology and are not very developer-friendly, so I personally use js-cookie, which is a small library to manipulate cookies. It makes it look like a simple key/value persistence system too: Cookies.set('access_token', value) then Cookies.get('access_token').
Other pro for the cookies: they are cross subdomains! If your Login app is login.mycompany.com and your Main app is app.mycompany.com, then you can create a cookie on the Login app and access it from the Main app. This is not possible with LocalStorage.
Here are some of the methods and special React components I use for authentication:
isAuthenticated()
import Cookies from 'js-cookie'
export const getAccessToken = () => Cookies.get('access_token')
export const getRefreshToken = () => Cookies.get('refresh_token')
export const isAuthenticated = () => !!getAccessToken()
authenticate()
export const authenticate = async () => {
if (getRefreshToken()) {
try {
const tokens = await refreshTokens() // call an API, returns tokens
const expires = (tokens.expires_in || 60 * 60) * 1000
const inOneHour = new Date(new Date().getTime() + expires)
// you will have the exact same setters in your Login page/app too
Cookies.set('access_token', tokens.access_token, { expires: inOneHour })
Cookies.set('refresh_token', tokens.refresh_token)
return true
} catch (error) {
redirectToLogin()
return false
}
}
redirectToLogin()
return false
}
redirectToLogin()
const redirectToLogin = () => {
window.location.replace(
`${getConfig().LOGIN_URL}?next=${window.location.href}`
)
// or history.push('/login') if your Login page is inside the same app
}
AuthenticatedRoute
export const AuthenticatedRoute = ({
component: Component,
exact,
path,
}) => (
<Route
exact={exact}
path={path}
render={props =>
isAuthenticated() ? (
<Component {...props} />
) : (
<AuthenticateBeforeRender render={() => <Component {...props} />} />
)
}
/>
)
AuthenticateBeforeRender
class AuthenticateBeforeRender extends Component {
state = {
isAuthenticated: false,
}
componentDidMount() {
authenticate().then(isAuthenticated => {
this.setState({ isAuthenticated })
})
}
render() {
return this.state.isAuthenticated ? this.props.render() : null
}
}