Is there a recommended way to implement Authenticated Routes? #364
Replies: 5 comments 4 replies
-
This has the 2nd most upvotes of all time with no response so here is a gentle bump in case it was forgotten about 😄 |
Beta Was this translation helpful? Give feedback.
-
After some more testing, I think using a config-based router is making is harder than it needs to be. I switch over to non-config-based and was able to do this: An AuthGuard component to wrap private routes function AuthGuard(props: ParentProps): JSX.Element {
const [isAuthenticated, setIsAuthenticated] = createSignal(false);
const navigate = useNavigate();
const location = useLocation();
async function performAuthCheck() {
setIsAuthenticated(false);
const auth = await wait(3000);
if (auth) {
setIsAuthenticated(true);
console.log(`Authenticated: ${new Date()}`);
} else {
navigate("/login");
}
}
createRenderEffect(on(() => location.pathname, performAuthCheck));
return (
<>
<Show when={isAuthenticated()} fallback={<div>FALLLBACK</div>}>
{props.children}
</Show>
</>
);
} Routes definition export function Router(props: RouterProps) {
return (
<SolidRouter {...props}>
{/* Public pages */}
<Route path="/" component={lazy(() => import("./pages/Home"))} />
<Route path="/login" component={() => <div>Login Page</div>} />
<Route path="/*404" component={lazy(() => import("./pages/NotFound"))} />
{/* Private / Authenticated pages */}
<Route path="/" component={AuthGuard}>
<Route path="/:entity">
<Route path="/" component={lazy(() => import("./pages/Profile"))} />
<Route
path="/reports"
component={lazy(() => import("./pages/Profile/UserReports"))}
/>
<Route
path="/projects"
component={lazy(() => import("./pages/Profile/UserProjects"))}
/>
</Route>
</Route>
</SolidRouter>
);
} |
Beta Was this translation helpful? Give feedback.
-
This is a setup that I use. AuthGuard is a layout component which does not affect the url. export default function AuthGuard(props: ParentProps) {
// will return true if authenticated, false if not, undefined when not fetched yet
const isAuthenticated = createAsync(() => getIsAuthenticated(), {
deferStream: true,
});
const location = useLocation();
onMount(() => {
function refreshWhenVisible() {
if (!document.hidden && isAuthenticated()) {
revalidate(getIsAuthenticated.key);
}
}
//revalidate after focus comes back to app's browser tab
document.addEventListener('visibilitychange', refreshWhenVisible);
//refresh the token before it expires
const interval = setInterval(refreshWhenVisible, 1000 * 60 * 15);
onCleanup(() => {
document.addEventListener('visibilitychange', refreshWhenVisible);
clearInterval(interval);
});
});
return (
<Suspense>
<Switch fallback={'checking auth...'}>
<Match when={isAuthenticated()}>{props.children}</Match>
<Match when={isAuthenticated() === false}>
{/* You could also render your Login component instead of redirecting */}
<Navigate href={`/login?redirectTo=${location.pathname}`} />
</Match>
</Switch>
</Suspense>
);
} Here's a stackblitz if you want to see it in action: |
Beta Was this translation helpful? Give feedback.
-
This is what I've been using to replace the requested page with a sign-in UX <Router>
<Route path="/" component={Home} />
<Route path="/:topic" component={App} />
<Route path="/:topic/edit" component={RequireAuth(Edit)} />
</Router> function RequireAuth(Component) {
return () => {
const app = useFirebaseApp();
const auth = getAuth(app);
const user = useAuth(auth);
return (
<Switch fallback={<SignIn />}>
<Match when={user.loading}>
<Loading />
</Match>
<Match when={user.data}>
<Component />
</Match>
</Switch>
);
};
} |
Beta Was this translation helpful? Give feedback.
-
Hey there, I might have found a better solution and why I think so. Simplicity and Flexibility as it requires just a few lines of code, making it extremely easy to maintain and use. It provides an Efficient Component Lifecycle because, unlike component-wrapper approaches (HOCs or layout components), this solution prevents unnecessary mounting and unmounting of components, improving performance. Additionally, it creates a Single Point of Authentication by adding the auth check at the top-level Route, meaning you only need to implement it once for all child routes. Plus using the query high order function from Solid/router the response is cached for 5 seconds and it provides deduplication for server requests. import {
Router,
Route,
redirect,
RoutePreloadFuncArgs,
} from "@solidjs/router"
const checkAuth = query(async function (
args: RoutePreloadFuncArgs,
auth: boolean
) {
"use server"
// COULD ALSO GET USER STATUS HERE
if (auth && args.location.pathname === "/auth") {
throw redirect("/")
}
if (!auth && args.location.pathname !== "/auth") {
throw redirect("/auth")
}
},
"checkAuth")
const App: Component = () => {
return (
<Router>
<Route
preload={args => createAsync(() => checkAuth(args, true))}
>
<Route path="/" component={Home} />
<Route path="/auth" component={Auth} />
{/* All other routes */}
</Route>
</Router>
)
} Optional: Lazy Loading Components// Same checkAuth function either with or without "query"
const App: Component = () => {
return (
<Router>
<Route
preload={async args => {
checkAuth(args, false)
}}
>
<Route path="/" component={lazy(() => import('./Home'))} />
<Route path="/auth" component={lazy(() => import('./Auth'))} />
{/* All other routes */}
</Route>
</Router>
)
} Benefits if you choose to use lazy loading:
Reference: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Assume you have a SPA that includes several public
Routes
and several privateRoutes
that require an authenticated user. Ideally, I'd like to wrap all of those protectedRoutes
under a single component that can validate the user every time the page/route is changed.Route
offersload:RouteLoadFunc
:But this implies I need to add some sort of "
verifyUser()
" to every singleRoute
I define. Is there a more canonical way to implement this in the latest version of solidjs-router? For a small number of routes this is manageable, but for a complicated or deeply nested route configuration, having to add that prop to everyRoute
isn't ideal.The goal is to verify the authentication status of the user on every route load, redirect or navigation.
Beta Was this translation helpful? Give feedback.
All reactions