kriasoft/react-starter-kit · error · TRPCError
UNAUTHORIZED
UNAUTHORIZED
Error message
Authentication required
What it means
protectedProcedure is tRPC middleware that requires an authenticated session. When ctx.session or ctx.user is null it throws a TRPCError with code UNAUTHORIZED before the procedure body runs, and narrows the context types so downstream code can use ctx.user safely. It is the standard tRPC pattern for guarding private procedures.
Source
Thrown at apps/api/lib/trpc.ts:55
TContext,
TMeta,
{
session: NonNullable<TRPCContext["session"]>;
user: NonNullable<TRPCContext["user"]>;
},
TInputIn,
TInputOut,
TOutputIn,
TOutputOut,
TCaller
>
: never;
export const protectedProcedure: ProtectedProcedure = t.procedure.use(
({ ctx, next }) => {
if (!ctx.session || !ctx.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Authentication required",
});
}
return next({
ctx: {
...ctx,
session: ctx.session,
user: ctx.user,
},
});
},
);
View on GitHub (pinned to 0aa7603435)
Solutions
- Sign in first (Better Auth sign-in flow) and ensure the session cookie/token is attached to subsequent requests
- Check the client sends credentials: fetch('/api/trpc/...', { credentials: 'include' }) or the appropriate Authorization header
- Verify cookie domain/SameSite settings allow the cookie on the API origin, especially in cross-worker routing setups
- Handle the UNAUTHORIZED code on the client by redirecting to login instead of showing a raw error
Example fix
// before
const data = await trpc.billing.subscription.query(); // no session attached
// after
await authClient.signIn.email({ email, password });
const data = await trpc.billing.subscription.query(undefined, { context: { credentials: 'include' } }); Defensive patterns
Strategy: try-catch
Validate before calling
// client-side pre-check before calling a protected procedure
const { data: session } = await authClient.useSession();
if (!session) {
redirect('/login?next=' + encodeURIComponent(currentPath));
} Type guard
function isUnauthorized(error: unknown): error is { code: 'UNAUTHORIZED'; message: string } {
return (
typeof error === 'object' && error !== null &&
'code' in error && (error as { code?: string }).code === 'UNAUTHORIZED'
);
} Try / catch
try {
return await trpc.billing.subscription.query();
} catch (error) {
if (isTRPCClientError(error) && error.data?.code === 'UNAUTHORIZED') {
await authClient.signOut();
window.location.href = '/login';
return;
}
throw error;
} Prevention
- Always attach credentials (cookies) to tRPC fetches; use credentials: 'include' cross-origin
- Proactively check session validity before rendering authenticated UI and refresh expiring sessions
- Catch UNAUTHORIZED globally in a tRPC link/middleware and redirect to login once, not per-call
- In tests, seed a session via the auth test helpers before using createCallerFactory
When it happens
Trigger: Calling any mutation/query built on protectedProcedure without a valid session cookie/Bearer token, with an expired session, or from a client that never forwarded credentials (e.g. missing credentials: 'include' on fetch).
Common situations: User's session expired while a tab stayed open, Better Auth cookie not sent cross-origin (wrong credentials mode or cookie domain), API client hitting the API worker directly without the auth header, or tests creating a caller without seeding a session.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of kriasoft/react-starter-kit@0aa7603435 (2026-08-31).
Data as JSON: /api/errors/dc393a8f23ac7937.
Report an issue: GitHub.