# tRPC

This is how your API handler would typically look like:

<pre class="language-ts" data-title="src/main.ts"><code class="lang-ts">import express from "express";
import * as fs from "node:fs/promises";
import { z } from "zod";
import { initTRPC } from "@trpc/server";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import type { Request } from "express";
import { bootstrapAuth, getUser } from "./auth"; // See below

function startExpressTrpcServer() {
<strong>    bootstrapAuth({
</strong><strong>        implementation: "real", // or "mock", see: https://docs.oidc-spa.dev/v/v8/integration-guides/backend-token-validation/mock-modes
</strong><strong>        issuerUri: process.env.OIDC_ISSUER_URI!,
</strong><strong>        expectedAudience: process.env.OIDC_AUDIENCE
</strong><strong>    });
</strong>
    const app = express();

<strong>    /**
</strong><strong>     * Key idea: Wether you use use Express or something else
</strong><strong>     * as underlying HTTP framework, just expose whatever the 
</strong><strong>     * request object representation is to the global context.
</strong><strong>     * oidc-spa will be able to extract the auth context from it.
</strong><strong>     */
</strong><strong>    const createContext = ({ req }: { req: Request }) => ({ req });
</strong>
    type Context = ReturnType&#x3C;typeof createContext>;

    const t = initTRPC.context&#x3C;Context>().create();

    const appRouter = t.router({
        todos: t.procedure.query(async ({ ctx }) => {
<strong>            const user = await getUser({ req: ctx.req });
</strong>            const json = await fs.readFile(
<strong>                `todos_${user.id}.json`, 
</strong>                "utf8"
            );
            return JSON.parse(json);
        }),

        todosForSupportStaff: t.procedure
            .input(z.object({ userId: z.string() }))
            .query(async ({ ctx, input }) => {
                // Will reject the request if user making the request
                // doesn't have "support-staff" role
<strong>                await getUser({ req: ctx.req, requiredRole: "support-staff" });
</strong>                const json = await fs.readFile(`todos_${input.userId}.json`, "utf8");
                return JSON.parse(json);
            })
    });

    app.use(
        "/trpc",
        // Needed for tRPC POST requests.
        express.json(),
        createExpressMiddleware({
            router: appRouter,
            createContext
        })
    );

    app.listen(parseInt(process.env.PORT ?? "3000"), () => {
        console.log("Server running");
    });
}
</code></pre>

Let's see how to export the utils to make it happen:

{% code title="src/auth.ts" %}

```ts
import { oidcSpa, extractRequestAuthContext } from "oidc-spa/server";
import { z } from "zod";
import { TRPCError } from "@trpc/server";
import type { Request } from "express";

const { bootstrapAuth, validateAndDecodeAccessToken } = oidcSpa
    .withExpectedDecodedAccessTokenShape({
        // This is purely declarative. Here you'll specify
        // the claim that you expect to be present in the access token payload.
        decodedAccessTokenSchema: z.object({
            sub: z.string(),
            name: z.string(),
            email: z.string().optional(),
            // Keycloak specific, convention to manage authorization.
            realm_access: z
                .object({
                    roles: z.array(z.string())
                })
                .optional()
        })
    })
    .createUtils();

export { bootstrapAuth };

// Your local representation of a user.
export type User = {
    id: string;
    name: string;
    email: string | undefined;
};

export async function getUser(params: {
    req: Request;
    requiredRole?: "realm-admin" | "support-staff";
}): Promise<User> {

    const { req, requiredRole } = params;

    const requestAuthContext = extractRequestAuthContext({
        // Here request accept any common representation of a request
        // Request | IncomingMessage | HonoRequest | FastifyRequest ...
        request: req,
        // Set this to false only if you don't have a reverse HTTP proxy in front of your
        // server. (Almost never the case in modern deployments).
        trustProxy: true
    });

    if (!requestAuthContext) {
        // Demo shortcut: we throw on missing Authorization, but a mixed
        // public/private procedure could instead return undefined here and let
        // the caller decide whether to process an anonymous request.
        console.warn("Anonymous request");
        throw new TRPCError({ code: "UNAUTHORIZED" });
    }

    if (!requestAuthContext.isWellFormed) {
        console.warn(requestAuthContext.debugErrorMessage);
        throw new TRPCError({ code: "BAD_REQUEST" });
    }

    const { isSuccess, debugErrorMessage, decodedAccessToken } =
        await validateAndDecodeAccessToken(
            requestAuthContext.accessTokenAndMetadata
        );

    if (!isSuccess) {
        console.warn(debugErrorMessage);
        throw new TRPCError({ code: "UNAUTHORIZED" });
    }

    // Your custom Authorization logic: Grant per request access depending
    // on the access token claim.
    if (requiredRole) {
        if (!decodedAccessToken.realm_access?.roles.includes(requiredRole)) {
            console.warn(`User missing role: ${requiredRole}`);
            throw new TRPCError({ code: "FORBIDDEN" });
        }
    }

    // Here you can potentially enrich the user object with additional
    // data that you would retrieve from your database if the access token
    // claim does not contain everything you need.

    const { sub, name, email } = decodedAccessToken;

    const user: User = { id: sub, name, email };

    return user;
}
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.oidc-spa.dev/docs/v8/integration-guides/backend-token-validation/trpc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
