# Web API

The primary usecase for a library like oidc-spa is to use it to authenticate a REST, tRPC, or Websocket API.

Let's see a very basic REST API example:

{% tabs %}
{% tab title="Vanilla API" %}
Initialize oidc-spa and expose the oidc instance as a promise:

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

```typescript
import { createOidc } from "oidc-spa";

export const prOidc = createOidc({/* ... */});
```

{% endcode %}

Create a REST API Client that adds the OIDC Access Token as Autorization header to every HTTP request:

<pre class="language-typescript" data-title="src/api.ts"><code class="lang-typescript">import axios from "axios";
<strong>import { prOidc } from "oidc";
</strong>
type Api = {
    getTodos: () => Promise&#x3C;{ id: number; title: string; }[]>;
    addTodo: (todo: { title: string; }) => Promise&#x3C;void>;
};

const axiosInstance = axios.create({ baseURL: import.meta.env.API_URL });

axiosInstance.interceptors.request.use(async config => {

<strong>    const oidc= await prOidc;
</strong><strong>
</strong><strong>    if( !oidc.isUserLoggedIn ){
</strong><strong>        throw new Error("We made a logic error: The user should be logged in at this point");
</strong><strong>    }
</strong><strong>    
</strong><strong>    config.headers.Authorization = `Bearer ${oidc.getTokens().accessToken}`;
</strong>
    return config;

});

export const api: Api = {
    getTodo: ()=> axiosInstance.get("/todo").then(response => response.data),
    addTodo: todo => axiosInstance.post("/todo", todo).then(response => response.data)
};
</code></pre>

{% endtab %}

{% tab title="React API" %}
Initialize the React adapter of oidc-spa and expose the prOidc object, a promise of the vanilla OIDC API:

<pre class="language-typescript" data-title="src/oidc.ts"><code class="lang-typescript">import { createReactOidc } from "oidc-spa/react";

export const { 
    OidcProvider, 
    useOidc,
<strong>    getOidc
</strong>} = createReactOidc(/* ... */);
</code></pre>

Create a REST API Client that adds the OIDC Access Token as Autorization header to every HTTP request:

<pre class="language-typescript" data-title="src/api.ts"><code class="lang-typescript">import axios from "axios";
<strong>import { getOidc } from "oidc";
</strong>
type Api = {
    getTodos: () => Promise&#x3C;{ id: number; title: string; }[]>;
    addTodo: (todo: { title: string; }) => Promise&#x3C;void>;
};

const axiosInstance = axios.create({ baseURL: import.meta.env.API_URL });

axiosInstance.interceptors.request.use(async config => {

<strong>    const oidc= await getOidc();
</strong><strong>
</strong><strong>    if( !oidc.isUserLoggedIn ){
</strong><strong>        throw new Error("We made a logic error: The user should be logged in at this point");
</strong><strong>    }
</strong><strong>    
</strong><strong>    config.headers.Authorization = `Bearer ${oidc.getTokens().accessToken}`;
</strong>
    return config;

});

export const api: Api = {
    getTodo: ()=> axiosInstance.get("/todo").then(response => response.data),
    addTodo: todo => axiosInstance.post("/todo", todo).then(response => response.data)
};
</code></pre>

Using your REST API client in your REACT components: &#x20;

<pre class="language-tsx"><code class="lang-tsx"><strong>import { api } from "../api";
</strong>
type Todo= {
    id: number; 
    title: string;
};

function UserTodos(){

    const [todos, setTodos] = useState&#x3C;Todo[] | undefined>(undefined);

    useEffect(
        ()=> {
<strong>            api.getTodos().then(todos => setTodos(todos));
</strong>        },
        []
    );

    if(todos === undefined){
        return &#x3C;>Loading your todos items ⌛️&#x3C;/>
    }

    return (
        &#x3C;ul>
            {todos.map(todo => (
                &#x3C;li key={todo.id}>{todo.title}&#x3C;/li>
            ))}
        &#x3C;/ul>
    );

}
</code></pre>

{% hint style="info" %}
This example is purposefully very basic to minimize noise but in your App you should consider using [tRPC](https://trpc.io/) (if you have JS backend) and [TanStack Query](https://tanstack.com/query/v3).
{% endhint %}
{% endtab %}
{% endtabs %}

## Backend

If you're implementing a JavaScript Backend (Node/Deno/webworker) oidc-spa also exposes an utility to help you validate and decode the access token that your client sends in the authorization header.  \
Granted, this is fully optional feel free to use anything else.  \
\
Let's assume we have a Node.js REST API build with Express or Hono.  \
You can create an oidc file as such:

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

```typescript
import { createOidcBackend } from "oidc-spa/backend";
import { z } from "zod";
import { HTTPException } from "hono/http-exception";

export async function createDecodeAccessToken() {

    const oidcIssuerUri = process.env.OIDC_ISSUER

    if (oidcIssuerUri === undefined) {
        throw new Error("OIDC_ISSUER must be defined in the environment variables")
    }

    const { verifyAndDecodeAccessToken } = await createOidcBackend({ 
        issuerUri: oidcIssuerUri,
        decodedAccessTokenSchema: z.object({
            sub: z.string(),
            realm_access: z.object({
                roles: z.array(z.string())
            })
            // Some other info you might want to read from the accessToken, example:
            // preferred_username: z.string()
        })
    });

    function decodeAccessToken(params: { 
        authorizationHeaderValue: string | undefined; 
        requiredRole?: string;
    }) {

        const { authorizationHeaderValue, requiredRole } = params;

        if( authorizationHeaderValue === undefined ){
            throw new HTTPException(401);
        }

        const result = verifyAndDecodeAccessToken({ 
            accessToken: authorizationHeaderValue.replace(/^Bearer /, "") 
        });

        if( !result.isValid ){
            switch( result.errorCase ){
                case "does not respect schema":
                    throw new Error(`The access token does not respect the schema ${result.errorMessage}`);
                case "invalid signature":
                case "expired":
                    throw new HTTPException(401);
            }
        }
        
        const { decodedAccessToken } = result;
        
        if( 
          requiredRole !== undefined &&
          !decodedAccessToken.ream_access.roles.includes(requiredRole)
        ){
            throw new HTTPException(401);
        }

        return decodedAccessToken;

    }

    return { decodeAccessToken };

}
```

{% endcode %}

Then you can enforce that some endpoints of your API requires the user to be authenticated, in this example we use Hono:&#x20;

<pre class="language-typescript"><code class="lang-typescript">import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi";
import { serve } from "@hono/node-server"
import { HTTPException } from "hono/http-exception";
import { getUserTodoStore } from "./todo";
<strong>import { createDecodeAccessToken } from "./oidc";
</strong>
(async function main() {

<strong>    const { decodeAccessToken } = await createDecodeAccessToken();
</strong>
    const app = new OpenAPIHono();

 
    {

        const route = createRoute({
            method: 'get',
            path: '/todos',
            responses: {/* ... */}
        });

        app.openapi(route, async c => {

<strong>            const decodedAccessToken = decodeAccessToken({
</strong><strong>                authorizationHeaderValue: c.req.header("Authorization")
</strong><strong>            });
</strong><strong>
</strong><strong>            if (decodedAccessToken === undefined) {
</strong><strong>                throw new HTTPException(401);
</strong><strong>            }
</strong>
            const todos = getUserTodoStore(decodedAccessToken.sub).getAll();

            return c.json(todos);

        });

    }

    const port = parseInt(process.env.PORT);

    serve({
        fetch: app.fetch,
        port
    })

    console.log(`\nServer running. OpenAPI documentation available at http://localhost:${port}/doc`)

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

## Comprehensive example

If you're looking for a comprehensive Backend+Frontend example you can refer to Insee's project&#x20;

{% embed url="<https://youtu.be/33VijFArY9s>" %}

The app is live here:

{% embed url="<https://vite-insee-starter.demo-domain.ovh/>" %}

The frontend (Vite project):

{% embed url="<https://github.com/InseeFrLab/vite-insee-starter>" %}

The backend (Node TODO App REST API):

{% embed url="<https://github.com/InseeFrLab/todo-rest-api>" %}


---

# 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/v4/documentation/web-api.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.
