Setting Up Protected Routes with Firebase and React
25/06/2022 • 4 min read
While we were working on some projects we had a discussion about handling routes in Firebase. So you will read about my approach in this post. Maybe you will get a new perspective or validate your own approach.
Determining The User Role for Authorization
In Firebase, users are connected to the Authentication module. That means authentication data is not in the application database. Therefore if you want to use the database for the users, you would have to manually handle users relationship with their data.
However, there is a concept called Custom Claims which is a part of The Firebase Admin SDK. Skipping the part where we setup user claims with the Admin SDK since this post is focused on the routing in the web UI.
Providing The User Roles to Your Application
Our project already had a provider setup. But to summarize it, please take a look at this code block:
// AuthProvider returns:
;<AuthContext.Provider
value={{ user, isAuthenticated: !!user, ...someOtherProps }}
children={children}
/>
// Export a hook to use it
export const useAuth = () => useContext(AuthContext)
Firebase Client library has a method called onAuthStateChanged
. First we get the user with it. Then we have to get the user claims. With the response from onAuthStateChanged
, we call getIdTokenResult
through it. This provides us with there properties:
- authTime
- claims
- expirationTime
- issuedAtTime
- signInProvider
- signInSecondFactor
- token
What we needed is claims and we got it. So we will assign the needed fields to our user
value. We only needed admin
so I did user.isAdmin = idTokenResult.claims.admin
. Here is the code:
// Should be initialized once with the provider,
// You can use this in a useEffect
firebase.auth().onAuthStateChanged((user) => {
if (user && onLogin) {
user.getIdTokenResult().then((idTokenResult) => {
user.isAdmin = idTokenResult.claims.admin
// Or mock it
// user.isAdmin = true; // TODO with Admin SDK
onLogin(user)
setUser(user)
})
} else setUser(user)
})
With this, we have can access wheter the user has the admin role by:
const { user } = useAuth()
console.log(user.isAdmin)
Setting Up The Route Component
Our goal is to create something like below:
// For all logged in users
<ProtectedRoute path="/profile" component={UserProfile} />
// For admins only
<ProtectedRoute isAdminRoute path="/admin" component={Admin} />
I had only added the user claim check to our higher-order component for authentication check. And it was done very similar to Auth0's component in their library.
We start by writing the HoC, but I am going to redact some pieces to make it simpler:
function withAuthenticationRequired(Component, options) {
return function WithAuthenticationRequired(props) {
// This will be passed from ProtectedRoute
const { isAdminRoute = false } = props
// Use our hook to get the user info
const { isAuthenticated, user } = useAuth()
const {
returnTo = defaultReturnTo, // a function that returns a url path
onRedirecting = defaultOnRedirecting, // returns a component that displays when redirecting
} = options
useEffect(() => {
let isAuthorized = false
if (isLoaded) {
/* If authenticated,
* Is the route admin only?
* If route is admin only, is the user */
isAuthorized = isAdminRoute ? !!user?.admin : isAuthenticated
const opts = {
appState: {
returnTo: typeof returnTo === "function" ? returnTo() : returnTo,
},
}
if (!isAuthenticated) history.push("/login", opts)
else if (!isAuthorized) history.push("/", opts) // because already logged in
}
}, [history, isAuthenticated, loginOptions, returnTo])
return isAuthenticated ? <Component {...props} /> : onRedirecting()
}
}
The ProtectedRoute
component, quite simple after what we wrote:
const ProtectedRoute = ({ component, ...args }) => {
// Wrap the component we get with the HoC we just wrote
const WrappedComponent = withAuthenticationRequired(component, {
onRedirecting: () => "Resuming the session…",
})
return (
<Route
render={(routeProps) => <WrappedComponent {...routeProps} {...args} />}
/>
)
}
And this matches our goal!
End
I have mentioned some conventions about Firebase, a React provider, a higher-order component and a route component. Hope this helps you. We did this implementation as a part of a Developer Acceleration Program (the actual name is vague at the moment :D) by Scott Coates thanks to him and everyone in it. Here is the GitHub organization connected to it.
"Setting Up Protected Routes with Firebase and React", 25/06/2022, 08:30:00