T
TanStack3y ago
genetic-orange

Handling High Level Auth With Hooks

👋 @Tanner Linsley so like i was getting at on twitter, I've been trying to figure out the right way to go about setting up auth with the router when doing something like the below. The example in the docs was fairly static, so was trying to figure out a larger solution. I've tried various things with beforeLoad and with context, but haven't been able to nail it down.
I've reeled back the below to a simple base that works, but needs to be cleaned up to a proper setup. Any thoughts on a best approach here? I'm fine redirecting to that login path when a route is unauthorized or a login page i can pretty up -- either works for me. Happy to provide more code, it's just hard to get something fully running on code sandbox since i'm using an IDP in docker for the auth. https://gist.github.com/pdevito3/190bff13bdf237fd285ec02735c03b6b
Gist
Tanstack Router Base WIP
Tanstack Router Base WIP. GitHub Gist: instantly share code, notes, and snippets.
42 Replies
rising-crimson
rising-crimson3y ago
I’ll look at this tonight What are you building with it?
genetic-orange
genetic-orangeOP3y ago
It’s just a vanilla vite app Using .NET for the BFF side of things
rising-crimson
rising-crimson3y ago
Woo!
genetic-orange
genetic-orangeOP3y ago
just realized my brain switched with it to it with lol. Trying to get a SPA template down for my OSS and want to dogfood a complete LIMS app for it (lab management software) since i have a long background there.
absent-sapphire
absent-sapphire3y ago
You don't need to create components for layouts create a route with id instead of path and use that as layout less imports and just make auth check there for all the child routes
absent-sapphire
absent-sapphire3y ago
Authenticated Routes | TanStack Router Docs
Authentication is an extremely common requirement for web applications. In this guide, we'll walk through how to use TanStack Router to build protected routes, and how to redirect users to login if they try to access them. The route.beforeLoad Option
rising-crimson
rising-crimson3y ago
@pdevito3 did you see that example?
genetic-orange
genetic-orangeOP3y ago
yeah, but how can i get isAuthenticated? i can't use my hook there
absent-sapphire
absent-sapphire3y ago
pull out function from hook
genetic-orange
genetic-orangeOP3y ago
yeah i went down that path a bit last night and was still having issues i'll give it another whirl, might just need fresh eyes
absent-sapphire
absent-sapphire3y ago
ping me if you need help with it
genetic-orange
genetic-orangeOP3y ago
hadn't seen this portion before. nice to see, but how does something like the below actually point to a particular layout?
const navigationLayout = new Route({
getParentRoute: () => rootRoute,
id: "navigation-layout",
});
const navigationLayout = new Route({
getParentRoute: () => rootRoute,
id: "navigation-layout",
});
absent-sapphire
absent-sapphire3y ago
Route Paths | TanStack Router Docs
Route paths are used to match parts of a URL's pathname to a route. At their core, route paths are just strings and can be defined using a variety of syntaxes, each with different behaviors. Before we get into those behaviors, lets look at a few important cross-cutting path concerns. Leading and Trailing Slashes
rising-crimson
rising-crimson3y ago
You pass a component with it
absent-sapphire
absent-sapphire3y ago
component: () => <Outlet /> or whatever you want like a navy bar something
genetic-orange
genetic-orangeOP3y ago
ah i had errors when i tried it but i hadn't changed children to outlet duh makes sense cool i'll give the auth piece another whirl and ping yall
absent-sapphire
absent-sapphire3y ago
if you need i have two examples of different methods of auth one with loaders and one with hooks
genetic-orange
genetic-orangeOP3y ago
would love to take a peek if you're able
absent-sapphire
absent-sapphire3y ago
GitHub
GitHub - bachiitter/dayplanr: Effortlessly manage your to-do list w...
Effortlessly manage your to-do list with a clean, intuitive interface that&#39;s easy to navigate. - GitHub - bachiitter/dayplanr: Effortlessly manage your to-do list with a clean, intuitive in...
absent-sapphire
absent-sapphire3y ago
GitHub
GitHub - bachiitter/quirk: An opinionated open-source template for ...
An opinionated open-source template for building full-stack web apps using React and Cloudflare Pages/Functions. - GitHub - bachiitter/quirk: An opinionated open-source template for building full-s...
genetic-orange
genetic-orangeOP3y ago
🍻
absent-sapphire
absent-sapphire3y ago
GitHub
dayplanr-v1/src/pages/app at main · bachiitter/dayplanr-v1
Effortlessly manage your to-do list with a clean, intuitive interface that's easy to navigate. - bachiitter/dayplanr-v1
absent-sapphire
absent-sapphire3y ago
forgot about this one that is based on nextjs app router pattern
genetic-orange
genetic-orangeOP3y ago
FYI (and i very well could have missed it in the docs), but for the layout stuff, it wasn't clear to me that i needed to nest this add children. your layout repo helped 🙂
const authLayout = new Route({
getParentRoute: () => appRoute,
id: "auth-layout",
component: AuthLayout,
});

const indexRoute = new Route({
getParentRoute: () => authLayout,
path: "/",
component: IndexPage,
});

const orderRoute = new Route({
getParentRoute: () => authLayout,
path: "/orders",
component: OrdersPage,
});

// Create the route tree using your routes
const routeTree = appRoute.addChildren([
authLayout.addChildren([indexRoute, orderRoute]),
]);
const authLayout = new Route({
getParentRoute: () => appRoute,
id: "auth-layout",
component: AuthLayout,
});

const indexRoute = new Route({
getParentRoute: () => authLayout,
path: "/",
component: IndexPage,
});

const orderRoute = new Route({
getParentRoute: () => authLayout,
path: "/orders",
component: OrdersPage,
});

// Create the route tree using your routes
const routeTree = appRoute.addChildren([
authLayout.addChildren([indexRoute, orderRoute]),
]);
about to dig through the auth stuff now
rising-crimson
rising-crimson3y ago
This is where a file based router would help a lot
absent-sapphire
absent-sapphire3y ago
Hey Tanner, may I rewrite docs for layouts and auth with improved examples
rising-crimson
rising-crimson3y ago
Absolutely!
genetic-orange
genetic-orangeOP3y ago
so i have my layout and auth hook like below (route tree above), but i'm having a couple problem: 1. every page navigation is triggering the loading. shouldn't it just be replacing the nested outlet and not causing a refetch? 2. trying to handle the unauthenticated path, but if i reroute it just gets in an infinite loop of not logged out even when i am (i think from the initial render where it hasn't got the data yet) i was able to do this in the api on error any thoughts?
export default function AuthLayout() {
const { isLoggedIn, user, logoutUrl, isLoading } = useAuthUser();
if (isLoading) return <Loading />;

return (
<>
<div>
<DesktopMenu user={user} logoutUrl={logoutUrl} />

<div className="sticky top-0 z-40 flex items-center px-4 py-4 bg-white shadow-sm gap-x-6 sm:px-6 lg:hidden">
<div className="flex-1 text-sm font-semibold leading-6 text-primary">
Peak LIMS
</div>

<ProfileManagement user={user} logoutUrl={logoutUrl} />
</div>

<main className="py-10 lg:pl-72">
<div className="px-4 sm:px-6 lg:px-8">
<Outlet />
</div>
</main>

<MobileMenu />
</div>
</>
);
}
export default function AuthLayout() {
const { isLoggedIn, user, logoutUrl, isLoading } = useAuthUser();
if (isLoading) return <Loading />;

return (
<>
<div>
<DesktopMenu user={user} logoutUrl={logoutUrl} />

<div className="sticky top-0 z-40 flex items-center px-4 py-4 bg-white shadow-sm gap-x-6 sm:px-6 lg:hidden">
<div className="flex-1 text-sm font-semibold leading-6 text-primary">
Peak LIMS
</div>

<ProfileManagement user={user} logoutUrl={logoutUrl} />
</div>

<main className="py-10 lg:pl-72">
<div className="px-4 sm:px-6 lg:px-8">
<Outlet />
</div>
</main>

<MobileMenu />
</div>
</>
);
}
const fetchClaims = async () =>
axios.get("/bff/user", config).then((res) => res.data);

function useClaims() {
return useQuery(
claimsKeys.claim,
async () => {
const delay = new Promise((resolve) => setTimeout(resolve, 550));
return Promise.all([fetchClaims(), delay]).then(([claims]) => claims);
},
{
retry: false,
}
);
}

function useAuthUser() {
const { data: claims, isLoading, isError } = useClaims();
if (isError) window.location.href = "/bff/login";

// does claims things

return {
user,
logoutUrl: logoutUrl?.value ?? undefined,
isLoading,
isLoggedIn,
};
}

export { useAuthUser };
const fetchClaims = async () =>
axios.get("/bff/user", config).then((res) => res.data);

function useClaims() {
return useQuery(
claimsKeys.claim,
async () => {
const delay = new Promise((resolve) => setTimeout(resolve, 550));
return Promise.all([fetchClaims(), delay]).then(([claims]) => claims);
},
{
retry: false,
}
);
}

function useAuthUser() {
const { data: claims, isLoading, isError } = useClaims();
if (isError) window.location.href = "/bff/login";

// does claims things

return {
user,
logoutUrl: logoutUrl?.value ?? undefined,
isLoading,
isLoggedIn,
};
}

export { useAuthUser };
ooo wait, i still have anchor tags in my nav. i bet that's it was updating my mobile nav for a hot minute, but once i updated desktop that did it 🚀 lol i am seeing this typescript unhappiness on Link though
Property 'search' is missing in type '{ children: (string | Element)[]; to: string; className: string; }' but required in type '{ search: SearchReducer<Partial<{}>, Partial<{}> & Partial<{}> & Omit<never, never>>; }'.ts(2741)
Property 'search' is missing in type '{ children: (string | Element)[]; to: string; className: string; }' but required in type '{ search: SearchReducer<Partial<{}>, Partial<{}> & Partial<{}> & Omit<never, never>>; }'.ts(2741)
<Link
to={item.href}
className={cn(
item.current
? "bg-gray-50 text-foreground"
: "text-gray-700 hover:text-foreground hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
<item.icon
className={cn(
item.current
? "text-foreground"
: "text-gray-400 group-hover:text-foreground",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
/>
{item.name}
</Link>
<Link
to={item.href}
className={cn(
item.current
? "bg-gray-50 text-foreground"
: "text-gray-700 hover:text-foreground hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
>
<item.icon
className={cn(
item.current
? "text-foreground"
: "text-gray-400 group-hover:text-foreground",
"h-6 w-6 shrink-0"
)}
aria-hidden="true"
/>
{item.name}
</Link>
rising-crimson
rising-crimson3y ago
Do one of the links in your navigation require search parameters?
genetic-orange
genetic-orangeOP3y ago
not as of yet but they very well could. i tweaked the desktop ones to look more like this atm. mobile ones need a little extra love with useNavigate to close the mobile menu on nav
<Link
to={item.href}
className={cn(
"text-gray-700 hover:text-foreground hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
activeProps={{
className: "bg-card text-emerald-500",
}}
>
<item.icon
className={cn("h-6 w-6 shrink-0")}
aria-hidden="true"
/>
{item.name}
</Link>
<Link
to={item.href}
className={cn(
"text-gray-700 hover:text-foreground hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
)}
activeProps={{
className: "bg-card text-emerald-500",
}}
>
<item.icon
className={cn("h-6 w-6 shrink-0")}
aria-hidden="true"
/>
{item.name}
</Link>
disregard my mixed colors. still need to figure out this shadcn setup lol
absent-sapphire
absent-sapphire3y ago
i thinks its because of type for href can you share snippet for type of item.href will write up on weekend
absent-sapphire
absent-sapphire3y ago
confirmed it because of incorrect types
No description
absent-sapphire
absent-sapphire3y ago
fix and you get intellisesnse ⬇️
import type { ReactNode } from "react";
import { Link } from "@tanstack/react-router";
import type { RegisteredRoutesInfo } from "@tanstack/react-router";

export function NavLink(item: {
href: RegisteredRoutesInfo["routePaths"];
name: ReactNode | string;
}) {
return <Link to={item.href}>{item.name}</Link>;
}
import type { ReactNode } from "react";
import { Link } from "@tanstack/react-router";
import type { RegisteredRoutesInfo } from "@tanstack/react-router";

export function NavLink(item: {
href: RegisteredRoutesInfo["routePaths"];
name: ReactNode | string;
}) {
return <Link to={item.href}>{item.name}</Link>;
}
genetic-orange
genetic-orangeOP3y ago
Yeah makes sense. Was going to try something along those lines but need to refactor a bit given my nav setup The error didn’t hint at that at all 😂 confirmed fixed thanks again
foreign-sapphire
foreign-sapphire2y ago
hi Bachitter, hope you dont mind me reviving an old thread but I have the exact same question as OP. I wanted to ask,is the alternative
You don't need to create components for layouts create a route with id instead of path and use that as layout less imports and just make auth check there for all the child routes
You don't need to create components for layouts create a route with id instead of path and use that as layout less imports and just make auth check there for all the child routes
comparable to checking auth in route.beforeLoad? Are there any tradeoffs?
absent-sapphire
absent-sapphire2y ago
All good, it just less code and cache session in layout if you're using that and so less calls and can be used with entire page tree below that layout
foreign-sapphire
foreign-sapphire2y ago
Thanks for answering. I hope you wont mind me asking a related question? For file based routing, say i have an ./app directory that holds all routes that requires authentication, could i do something like using an index.tsx in ./app that checks user's auth status through an auth context and only return an <Outlet /> ?
absent-sapphire
absent-sapphire2y ago
should be layout file returning an Outlet, I haven't used file based routing works so i don't know the naming conventions tho
foreign-sapphire
foreign-sapphire2y ago
thank so much for your help. i was also wondering if i may take a look at these sample repos you shared above?
absent-sapphire
absent-sapphire2y ago
later after work i will create a new one and put up the code to GitHub as the repos were using early beta version
like-gold
like-gold2y ago
Hey, looking for examples on auth also. Trying to implement oAuth flow and my api call in the auth callback page's loader is being called twice 🤷‍♂️ Was hoping to see some more examples of auth in TSRouter. Do you happen to have any time to upload those example?
molecular-blue
molecular-blue2y ago
Using the react-oidc-context package, this was the set up that worked for me. Notable files are: src/routes/__root.tsx src/routes/oidc-callback.route.tsx src/react-oidc-context.config.ts Other than that, I just check the auth status in the loader before letting the API call go through, but that's just a single line if statement. I could explore doing this router context, but I haven't gotten around to trying it yet. example src/routes/agreements/loader.ts LN#6 https://github.com/SeanCassiere/nv-rental-clone/blob/d2228a535626025afdafef2e431c39bdd09e2f3f/src/routes/__root.tsx#L44
GitHub
nv-rental-clone/src/routes/__root.tsx at d2228a535626025afdafef2e43...
Navotar with Tailwind and the Tanstack. Contribute to SeanCassiere/nv-rental-clone development by creating an account on GitHub.

Did you find this page helpful?