It's quite common feature these days to have multiple user roles and based upon the user role we grant him access to different features of the application. Let's see how to create a similar feature in a React + MobX application along with react-router
Packages used are-
High level steps to follow-
Create a json config of all the routes of your application.
Create another config of roleAccess.
Create a mobx router store.
Call the updateAccess() @action of router store with user data. Thus, setting the required access based on the user role.
Use react-router and sync it's history with mobx router store using mobx-react-router
Setup ProtectedRoutes component which verifies authentication from auth store.
export const allPages = {
home: {
component: lazy(() => import(/* webpackChunkName: "home" */ '../containers/home')),
path: '/',
exact: true,
},
about: {
component: lazy(() => import(/* webpackChunkName: "about" */ '../containers/about')),
path: '/about',
},
contact: {
component: lazy(() => import(/* webpackChunkName: "contact" */ '../containers/contact')),
path: '/contact',
}
}
}```
```javascript
const roleAccess = {
admin: {
page: [home, about, contact],
},
editor: {
page: [home, about],
},
contributor: {
page: [home],
},
}
class RouterStore extends MobxRouterStore {
@observable pages = []
hasAccess = (path) => this.pages.includes(path)
@action
updateAccess = (user) => {
const role = (user.role || lastRole).toLowerCase()
this.pages = roleAccess[role].page
}
}
const ReactRouter = (props) => (
<Router history={syncHistoryWithStore(createBrowserHistory(), router)}>
<Switch>
<Route path="/login" component={Login} {...props} />
<ProtectedRoutes path="" component={AppRoutes} {...props} />
</Switch>
</Router>
)
const AppRoutes = inject('router')(({ router: { pages } }) => {
return (
<App>
<Suspense fallback={<LoadingSvg />}>
<Switch>
<NestedRoutes routes={pages} />
<Route component={() => <div>Not Found</div>} />
</Switch>
</Suspense>
</App>
)
})
const NestedRoutes = ({ routes }) =>
routes.map(({ component: Component, routes: nestedRoutes, switchRoutes, ...rest }) => {
const component = (props) => (
<>
<Component {...props} {...rest} />
{nestedRoutes && <NestedRoutes routes={nestedRoutes} />}
</>
)
return <Route {...rest} render={component} key={rest.path} />
})
@inject('auth')
@observer
class ProtectedRoutes extends Component {
render() {
const {
component: Component,
auth: { isAuthenticated, isAuthenticating },
...rest
} = this.props
if (isAuthenticating) {
return null
}
return (
<Route
{...rest}
render={props =>
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
}
/>
)
}
const GET_ME = gql`
{
me {
email
firstName
lastName
role
id
}
}
`
class AuthStore {
@observable googleAuthUrl = process.env.AUTH_URL
@observable isAuthenticated = false
@observable isAuthenticating = false
@observable token = null
setAuthToken = (token) => {
let tokenParam
let currentUrl
if (!token) {
currentUrl = new URL(window.location.href)
tokenParam = currentUrl.searchParams.get('token')
token = tokenParam || localStorage.get('token')
}
if (token) {
localStorage.set('token', token)
this.token = token
}
// remove tokens from the url
if (tokenParam) {
currentUrl.searchParams.delete('token')
window.history.replaceState(window.history.state, window.title, currentUrl.href)
}
return token
}
@action
logout = () => {
localStorage.remove('token')
this.isAuthenticated = false
}
@action
checkAuthToken = async () => {
this.isAuthenticating = true
// eslint-disable-next-line no-console
const response = await graphqlClient.query({ query: GET_ME }).catch((err) => console.error(err))
this.isAuthenticated = !!response
if (this.isAuthenticated) {
router.updateAccess(response.data.me)
}
this.isAuthenticating = false
return response && response.data && response.data.me
}
}
- Ayush 🙂