Essentially, I have a higher order component (HoC), that takes routes, and determines whether or not the currently authenticated user can access the component passed in via props.
Im trying to have a useEffect in my HoC that checks the ID token result of a user, and extracts the custom claims on the user, which were created for the user on the server side at the time of creation using the firebaseAdmin SDK, the custom claims are just the following:
{trial: true, isSubscribed: false}
Next it stores the value of these custom claims in the state for the component and uses a series of if else statements to determine whether the user is authenticated, and whether or not the user is subscribed if the route trying to be accessed is one requiring a subscription.
Lastly in the return method of my HoC, I render the component conditionally using a ternary operator.
return (
<>
{isAuthenticated? (
props.component
) : (
<Navigate to='/' />
)}
</>
)
Here is my app.js where I pass in the protected routes into my Hoc which is ProtectedPage as a component prop, I also specify whether or not the route going into ProtectedPage requires a subscription or not using the isSubcribedRoute prop:
//App.js
import "./styles/bootstrap.css";
import Home from './components/Home'
import Signup from './components/Signup';
import Login from './components/Login'
import LandingPage from './components/LandingPage'
import Account from './components/Account/Account';
import UserDetails from './components/Account/UserDetails';
import Quizzes from './components/Quizzes';
import Lessons from './components/Lessons';
import Learn from './components/Learn/Learn';
import LearnCourses from './components/Learn/LearnCourses';
import Features from './components/Features'
import ProtectedPage from './components/ProtectedPage';
import { FbMethodContextProvider } from "./firebase/fbMethodContext";
import { createBrowserRouter, RouterProvider} from 'react-router-dom';
import { AuthContextProvider, } from "./firebase/authContext";
const router = createBrowserRouter([
{
path: '/',
element: <Home />,
children: [
{
index: true,
element: <LandingPage />
},
{
path: '/signup',
element: <Signup />
},
{
path: '/login',
element: <Login />
},
{
path:'/features',
element: <ProtectedPage component={<Features />} isSubscribedRoute={false } />
}
]
},
{
path: '/account',
element: <ProtectedPage component={ <Account />} isSubscribedRoute={true}/>,
children:[
{
index: true,
element: <UserDetails/>,
}
]
},
{
path: '/quizzes',
element: <Quizzes />,
},
{
path: '/lessons',
element: <Lessons />
},
{
path: '/learn',
element: <ProtectedPage component ={<Learn />} isSubscribedRoute={false}/>,
children:[
{
index: true,
element: <LearnCourses />
}
]
}
]);
function App() {
return (
<FbMethodContextProvider>
<AuthContextProvider>
<RouterProvider router={router}/>
</AuthContextProvider>
</FbMethodContextProvider>
);
}
export default App;
Next this is my protected page component:
//ProtectedPage.js
import React,{useEffect, useState} from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import {auth} from '../firebase/firebaseConfig';
import {redirect , useNavigate, Navigate} from 'react-router-dom';
const ProtectedPage = (props) => {
const [user, setUser] = useState(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [trial, setTrial] = useState()
const [isSubscribed, setIsSubscribed] = useState()
const isSubscribedRoute = props.isSubscribedRoute
const navigate = useNavigate();
useEffect(()=>{
onAuthStateChanged(auth, async (user) =>{
if(user){
const idTokenResult = await user.getIdTokenResult(true);
const onTrial = idTokenResult.claims.trial;
setTrial(onTrial);
console.log(trial);
const Subscribed = idTokenResult.claims.subscribed;
setIsSubscribed(Subscribed)
console.log(isSubscribed)
console.log(`Trial is ${onTrial} & Subscribed is ${isSubscribed}`)
setUser(user);
if(trial || isSubscribed) {
setIsAuthenticated(true);
}
} else {
setUser(user)
}
})
let isAuthorized;
if ( isSubscribedRoute) {
if(isSubscribed){
isAuthorized = true;
console.log('user is subscribed and can access this page')
} else if (trial && isSubscribed === false){
navigate('/login')
}
}
if (!isAuthenticated){
console.log('not authenticated')
navigate('/login')
//The code works when I replace this call to the navigate hook above with react-router-dom's redirect, and replace the <Navigate /> component in the render method below with <div></div>
} else if(!isSubscribedRoute && trial){
isAuthorized = true;
}else if(!isAuthorized){
console.log('not authorized')
navigate('/login')
}
} )
return (
<>
{isAuthenticated? (
props.component
) : (
<Navigate to='/' />
)}
</>
)
}
export default ProtectedPage
Essentially whenever a user logs in or signsup it should navigate them to the learn page. It all worked fine before I tried to control access with custom claims. Now the only way I can get it to work is by replacing the navigate with redirect('/') in the if(!isAuthenticated) code block and replacing the <Navigate =to '/' /> with in the return function at the bottom.
Any help will be appreciated, or if any better methods or patterns are available please let me know as Im quite new to coding and don't know if my method is even good.