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

  1. Sign in first (Better Auth sign-in flow) and ensure the session cookie/token is attached to subsequent requests
  2. Check the client sends credentials: fetch('/api/trpc/...', { credentials: 'include' }) or the appropriate Authorization header
  3. Verify cookie domain/SameSite settings allow the cookie on the API origin, especially in cross-worker routing setups
  4. 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

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

Related errors


AI-assisted analysis of kriasoft/react-starter-kit@0aa7603435 (2026-08-31). Data as JSON: /api/errors/dc393a8f23ac7937. Report an issue: GitHub.