Hello! Welcome back to your course on mastering TanStack Start.
Introduction
In our previous lessons, we've successfully built a complete authentication flow. We can now:
- Securely store a JWT in an HTTP-only cookie.
- Create a typed authentication context available on both the server and client.
- Protect routes using a
beforeLoadcheck in a layout route. - Seamlessly redirect users back to their intended page after they log in.
The result of this work is that for any protected route, our router's context now contains the authenticated user's identity. The big question is: what do we do with it?
This lesson addresses the learning outcome: Access authenticated user data from the validated context within server functions and route loaders. We will explore the two primary scenarios where you'll need this data: fetching data specific to the user on the server (loaders) and performing actions on behalf of the user (server functions). This is where the benefits of our server-centric authentication model truly shine.
1. Accessing User Data in Route Loaders
The most common use case for authenticated user data is to fetch information that only they should see. Think of a user dashboard, a profile page, or a list of their own posts. TanStack Router makes this straightforward by passing context down from parent routes to child routes.
Recall our _authed.tsx layout route from a previous lesson. Its beforeLoad function fetches the user and returns it, making it available to all nested routes.
// src/routes/_authed.tsx (from a previous lesson)
export const Route = createFileRoute('/_authed')({
beforeLoad: async ({ location }) => {
// This function gets user data from the session
const user = await getCurrentUserFn();
if (!user) {
// Redirect if not authenticated
throw redirect({ to: '/login', search: { redirect: location.href } });
}
// Pass the validated user object to the context
return { user };
},
component: AuthedLayoutComponent, // Renders an <Outlet />
});
Now, any route inside the /_authed/ directory can access this user object.
The official TanStack documentation provides a perfect example of how to use this context within a child route's component.
Authentication | TanStack Start React Docs
The official TanStack authentication guide shows exactly how a child route can access context provided by a parent. We'll focus on the dashboard.tsx example.
Please review the code block for routes/_authed/dashboard.tsx. Notice how Route.useRouteContext() is used within the component to access the user object that was passed down from the _authed.tsx layout route.
From Component Context to Loader Context
The example you just saw uses Route.useRouteContext() to get the user data directly in the component. This is great for simple display purposes. However, to fetch data on the server before the page renders, we need to access this context inside a loader.
Let's expand on the dashboard example. Imagine we need to fetch dashboard-specific data for the logged-in user.
// src/routes/_authed/dashboard.tsx
import { createFileRoute } from '@tanstack/react-router';
import { getDashboardDataForUser } from '../../server/api'; // A hypothetical server function
// The user object is automatically typed from the parent route's context!
export const Route = createFileRoute('/_authed/dashboard')({
// 1. Define a loader function
loader: async ({ context }) => {
// 2. Access the user object from the context
const { user } = context;
// 3. Use the user's ID to fetch their specific data on the server
const dashboardData = await getDashboardDataForUser(user.id);
// 4. Return the data
return { dashboardData };
},
component: DashboardComponent,
});
function DashboardComponent() {
// 5. Access the loader data in the component
const { dashboardData } = Route.useLoaderData();
const { user } = Route.useRouteContext(); // Still available here too!
return (
<div>
<h1>Welcome, {user.email}!</h1>
<p>Your latest activity: {dashboardData.latestActivity}</p>
{/* ... render the rest of the dashboard */}
</div>
);
}
Key Points:
- The
loaderfunction receives acontextobject as an argument. - This
contextcontains all data returned from thebeforeLoadandloaderfunctions of its parent routes. We simply destructureuserfrom it. - We use
user.idto make a secure, server-side data request. The client is never trusted with providing this ID. - The data returned from the loader is made available to the component via the
Route.useLoaderData()hook.
2. Accessing User Data in Server Functions
While loaders are for fetching data, server functions are for handling mutations (e.g., creating, updating, or deleting data). When a user submits a form to create a post, the server function handling that request must know who the author is.
Server functions do not automatically inherit the router context. Instead, we secure them using middleware. This middleware runs before your server function's logic, validates the user's session, and injects the user's data into the function's context.
This video provides an excellent walkthrough of this exact pattern.
TanStack Start with Drizzle & Better Auth
In this clip from "TanStack Start with Drizzle & Better Auth," Dev Leonardo demonstrates how to use middleware to inject a validated user session into a server function's context, making it available for business logic.
Watch from 07:29 to 09:36. Pay close attention to how a userRequiredMiddleware is created and then attached to the joinCommunity server function. See how this makes context.user available inside the function's handler.
Implementing Middleware for Server Functions
Let's break down the process shown in the video.
Step 1: Create the Authentication Middleware
This middleware is a function that wraps our server function handler. Its job is to get the user and pass it along.
// src/server/authMiddleware.ts
import { serverFn } from '@tanstack/react-start';
import { getCurrentUserFn } from './auth'; // Our centralized function to get the user
export const authMiddleware = serverFn.middleware(async (req, res, next) => {
const user = await getCurrentUserFn();
if (!user) {
// If no user, end the request with a 401 Unauthorized status
res.statusCode = 401;
res.end('Unauthorized');
return; // Stop processing
}
// If user exists, pass it in the context to the next function
return next({
...next.ctx, // Preserve any existing context
user: user, // Add our user object
});
});
Step 2: Apply the Middleware to a Server Function
Now, we can create a server function and protect it with our new middleware.
// src/server/postActions.ts
import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
import { authMiddleware } from './authMiddleware';
import { db } from './db'; // Your database client
export const createPostFn = createServerFn({ method: 'POST' })
// 1. Apply the middleware
.use(authMiddleware)
// Define input validation
.inputValidator(z.object({ title: z.string(), content: z.string() }))
// 2. The handler now receives a typed `context` with the user
.handler(async ({ input, context }) => {
const { title, content } = input;
// 3. Securely get the user ID from the context injected by the middleware
const userId = context.user.id;
// Create the post associated with the authenticated user
const newPost = await db.post.create({
data: {
title,
content,
authorId: userId,
},
});
return { success: true, postId: newPost.id };
});
With this pattern, it's impossible for the createPostFn handler to run without a validated user. The context.user object is guaranteed to be present and is derived securely from the server-side session, not from any client-side input.
This video from Web Dev Cody offers another great perspective on the same concept, reinforcing its importance.
How I Protect My Tanstack Start Applications
This clip from "How I Protect My Tanstack Start Applications" also covers securing server functions. It's a good reinforcement of the middleware pattern we just discussed.
Watch from 03:16 to 04:42. Note how middleware is used to get user information from the session token and pass it into the server function's context, enabling dynamic backend logic.
Conclusion
You have now mastered the two fundamental patterns for using authenticated user data in a TanStack Start application. This is the payoff for setting up your authentication system correctly.
Key Takeaways:
- For Route Loaders: Access user data via the
contextobject passed to theloaderfunction. This data is inherited from parent routes'beforeLoadorloaderfunctions. - For Server Functions: Use middleware to intercept the request, validate the user's session, and inject the user object into the server function's
context. This is the secure way to perform actions on behalf of a user. - Centralized Logic: Both patterns should rely on a single, centralized function (e.g.,
getCurrentUserFn) to retrieve the user from the session. This keeps your authentication logic DRY and easy to maintain.
Next Up:
This lesson concludes our module on core JWT authentication. While our access tokens work, they are typically short-lived for security. In the next module, "Advanced Session Management & Logout," we will tackle this by implementing refresh tokens. You will learn how to automatically refresh an expired access token without requiring the user to log in again, creating a truly seamless and persistent session.
Can't find a good explanation? Sign up and we'll make it for you
Sign up