I am currently trying to set up my routing for a new project. What I am attempting to implement is if a user access /home/user/settings
and they are not logged in, it will redirect them to /login?redirectTo=/home/user/settings
.
Or if they go to /home/device/claim?deviceid=123456789
and they are not logged in they will get redirected to /login?redirectTo=/home/device/claim?deviceid=123456789
.
I currently have it working but I feel it cant be the best way of doing this. An issues I ran into when coming up with this idea were I don't know how many levels the URL will have.
My current code:
<Route>
<Route
path=":path1/:path2/:path3/:path4"
element={<RedirectAfterAuth />}
/>
<Route
path=":path1/:path2/:path3"
element={<RedirectAfterAuth />}
/>
<Route path=":path1/:path2" element={<RedirectAfterAuth />} />
<Route path=":path1" element={<RedirectAfterAuth />} />
<Route path="*" element={<Navigate to="/login" />} />
<Route path="login" element={<Login />} />
<Route path="/" element={<LandingPage />} />
</Route>
export default function RedirectAfterAuth() {
let { path1, path2, path3, path4 } = useParams();
let [searchParams, setSearchParams] = useSearchParams();
return (
<Navigate
to={`/login?redirectTo=${path1}${path2 ? "/" + path2 : ""}${
path3 ? "/" + path3 : ""
}${path4 ? "/" + path4 : ""}${searchParams ? "?" + searchParams : ""}`}
/>
);
}
I was wondering if there was a way to not have to put loads of different paths and account for all possibilities by just have a single param/searchparam.
Thank you for your time
You don't need to capture all the path segments when redirecting to an authentication route, you can simply capture the current location object and forward that as a "referrer" value for the login component to redirect back.
Create an authentication layout route component that will be used to protect routes, capture the location, and redirect to the auth route.
Example:
import { Navigate, Outlet, useLocation } from 'react-router-dom';
const AuthLayout = () => {
const location = useLocation(); // <-- current location being accessed
const { isAuthenticated } = /* auth state from local state/context/redux/etc */
return isAuthenticated
? <Outlet />
: (
<Navigate
to="/login"
replace // <-- redirect
state={{ from: location }} // <-- forward location
/>
);
};
export default AuthLayout;
Import and wrap the routes you want to protect with the authentication layout route.
Example:
import AuthLayout from '../path/to/AuthLayout';
...
<Router>
{/* Unprotected routes */}
<Route path="login" element={<Login />} />
<Route path="/" element={<LandingPage />} />
...
<Route element={<AuthLayout />}>
{/* Protected routes */}
<Route path="/profile" element={<Profile />} />
...
</Route>
</Router>
To redirect back to the original route being accessed, the login function accesses the pathname
and search
(and any route state) that was forwarded when getting redirected.
Example:
import { useLocation, useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
const location = useLocation();
...
const loginHandler = async e => {
e.preventDefault();
try {
await /* call authentication endpoint/set state/etc */
// auth success
const { from } = location.state || {};
const { pathname, search, state } = from || {};
navigate(
(pathname ?? "/") + (search ?? ""),
{
replace: true,
state
}
);
} catch (error) {
}
};
...