This document serves as an annotated guide to the official react router documentation on
restricted routes.
To start, we need to setup a base app with a few routes and a couple links to navigate.
language=>JSX
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom'
const AuthExample = () => (
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
)
// Add the other functions and components here
export default AuthExample
To make this example app work, we need to build the following components and functions.
- fakeAuth - an object that emulates authentication
- AuthButton - a component that handles signing out and provides login status
- PrivateRoute - a higher order component that verifies whether user is logged in
- Login - a component page that lets the user login
- Public - a component page that shows the public page
- Protected - a component page that shows the protected page
fakeAuth
We need an object to emulate logging in, signing out, and provides an authentication state. To do this, we create the fakeAuth object with three properties:
- isAuthenticated - stores the login state (true or false)
- authenticate - [function (cb)] a function that accepts a callback function, will change isAuthenticated to true and execute the callback function after 100 milliseconds.
- signout - [function (cb)] a function that accepts a callback function, will change isAuthenticated to false and execute the callback function after 100 milliseconds.
The object looks like this:
language=>JSX
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100) // fake async
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
In production scenario, you are likely to keep the isAuthenticated state (session state probably a better name) in a redux store so it can be easily shared across the application. The authenticate and signout functions will be making actual API requests to the server to perform those processes.
AuthButton
The AuthButton component will show a simple message prompting the login status, and display a signout button if the user is already signed in. A simple function component will do the job as there is no state required in this component. We wrap the component with withRouter function so it will have access to the router prop history so it can change the url to root / when the user signs out.
language=>JSX
const AuthButton = withRouter(({ history }) => (
fakeAuth.isAuthenticated ? (
<p>
Welcome! <button onClick={() => {
fakeAuth.signout(() => history.push('/'))
}}>Sign out</button>
</p>
) : (
<p>You are not logged in.</p>
)
))
In production scenario, you will probably change the "You are not logged in." paragraph to a login button instead.
PrivateRoute
The PrivateRoute component is a higher order component, which means it accepts another component as a prop and returns a component. Its use is to simply check whether the user is logged in and decides whether to return the restricted component or redirect the user to the login page. At its heart, it uses a Route component from react-router-dom. The path prop is passed to Route via the ...rest object spread. If the user is not logged in, a Redirect component is returned that brings the user to the login page. A state prop with the current location data is passed to redirect so that after the user logs in, we can redirect the user to the restricted page. Here is the code.
language=>JSX
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
Login
The Login component displays a login button, and handles the redirection to the restricted page after login. It uses a state to determine execution of the redirection. After the user clicks login, it calls fakeAuth.authenticate and passes it a callback that will change the state property redirectToReferrer to true. After the state change, the component re-renders and a Redirect component is returned to bring the user back to the restricted page.
language=>JSX
class Login extends React.Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
Public and Protected page
These two are simple, we just need a h3 to provide a prompt which page the user is on.
language=>JSX
const Public = () => <h3>Public</h3>
const Protected = () => <h3>Protected</h3>
That's all you need to setup a restricted route with React Router. The full code is pasted below for review.
language=>JSX
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom'
const AuthExample = () => (
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
)
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100) // fake async
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const AuthButton = withRouter(({ history }) => (
fakeAuth.isAuthenticated ? (
<p>
Welcome! <button onClick={() => {
fakeAuth.signout(() => history.push('/'))
}}>Sign out</button>
</p>
) : (
<p>You are not logged in.</p>
)
))
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
const Public = () => <h3>Public</h3>
const Protected = () => <h3>Protected</h3>
class Login extends React.Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true })
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer) {
return (
<Redirect to={from}/>
)
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
export default AuthExample