# NestJS

{% hint style="info" %}
If you prefer a more "Nestish" experience, there's a comunity wrapper around oidc-spa/server:

<https://github.com/mwolf1989/nestjs-spa-oidc>
{% endhint %}

This is how your Nest API would typically look like.

<pre class="language-ts" data-title="src/main.ts"><code class="lang-ts">import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { bootstrapAuth } from "./auth"; // See below
+import { ConfigService } from "@nestjs/config";

async function bootstrap() {
    const app = await NestFactory.create(AppModule, /* Any adapter */);

    // Requires ConfigModule.forRoot() somewhere in your imports (typically AppModule).
    const configService = app.get(ConfigService);

<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: configService.get("OIDC_ISSUER_URI")!,
</strong><strong>        expectedAudience: configService.get("OIDC_AUDIENCE")
</strong><strong>    });
</strong>

    await app.listen(parseInt(configService.get("PORT") ?? "3000"));
}

bootstrap();
</code></pre>

And this is how your controlled would look:

<pre class="language-ts" data-title="src/todos.controller.ts"><code class="lang-ts">import * as fs from "node:fs/promises";
import { Controller, Get, Param, Req } from "@nestjs/common";
<strong>import { getUser } from "./auth";
</strong>
@Controller("api")
export class TodosController {
    @Get("todos")
    async getTodos(@Req() req) {
<strong>        const user = await getUser({ req });
</strong>        const json = await fs.readFile(`todos_${user.id}.json`, "utf8");
        return JSON.parse(json);
    }

    @Get("todos-for-support/:userId")
    async getTodosForSupportStaff(
        @Req() req, 
        @Param("userId") userId: string
    ) {
<strong>        // Will reject the request if user making the request
</strong><strong>        // doesn't have "support-staff" role.
</strong><strong>        await getUser({ req, requiredRole: "support-staff" });
</strong>        const json = await fs.readFile(`todos_${userId}.json`, "utf8");
        return JSON.parse(json);
    }
}
</code></pre>

This is the only “integration” code you need:

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

```ts
import { BadRequestException, ForbiddenException, UnauthorizedException } from "@nestjs/common";
import { oidcSpa, extractRequestAuthContext, type AnyRequest } from "oidc-spa/server";
import { z } from "zod";

const { bootstrapAuth, validateAndDecodeAccessToken } = oidcSpa
    .withExpectedDecodedAccessTokenShape({
        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 respresentation of a user
export type User = {
    id: string;
    name: string;
    email: string | undefined;
};

export async function getUser(params: {
    // This can be an Express Request object, a FastifyRequest object
    // or really any well know object that represent a request,
    // oidc-spa will normalize the representation internally.
    // so this function will work regardless of the HTTP framework
    // you're using to bootstrap your NestJS app.
    req: AnyRequest;
    requiredRole?: "realm-admin" | "support-staff";
}): Promise<User> {

    const { req, requiredRole } = params;

    const requestAuthContext = extractRequestAuthContext({
        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) {
        console.warn("Anonymous request");
        throw new UnauthorizedException();
    }

    if (!requestAuthContext.isWellFormed) {
        console.warn(requestAuthContext.debugErrorMessage);
        throw new BadRequestException();
    }

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

    if (!isSuccess) {
        console.warn(debugErrorMessage);
        throw new UnauthorizedException();
    }

    if (requiredRole) {
        if (!decodedAccessToken.realm_access?.roles.includes(requiredRole)) {
            console.warn(`User missing role: ${requiredRole}`);
            throw new ForbiddenException();
        }
    }

    const { sub, name, email } = decodedAccessToken;

    return { id: sub, name, email };
}
```

{% 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/nestjs.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.
