Create a REST API Client that adds the OIDC Access Token as Autorization header to every HTTP request:
src/api.ts
import axios from"axios";import { prOidc } from"oidc";typeApi= {getTodos: () =>Promise<{ id:number; title:string; }[]>;addTodo: (todo: { title:string; }) =>Promise<void>;};constaxiosInstance=axios.create({ baseURL:import.meta.env.API_URL });axiosInstance.interceptors.request.use(async config => {constoidc=await prOidc;if( !oidc.isUserLoggedIn ){thrownewError("We made a logic error: The user should be logged in at this point"); }config.headers.Authorization =`Bearer ${oidc.getTokens().accessToken}`;return config;});exportconstapi:Api= {getTodo: ()=>axiosInstance.get("/todo").then(response =>response.data),addTodo: todo =>axiosInstance.post("/todo", todo).then(response =>response.data)};
Initialize the React adapter of oidc-spa and expose the prOidc object, a promise of the vanilla OIDC API:
Create a REST API Client that adds the OIDC Access Token as Autorization header to every HTTP request:
src/api.ts
import axios from"axios";import { getOidc } from"oidc";typeApi= {getTodos: () =>Promise<{ id:number; title:string; }[]>;addTodo: (todo: { title:string; }) =>Promise<void>;};constaxiosInstance=axios.create({ baseURL:import.meta.env.API_URL });axiosInstance.interceptors.request.use(async config => {constoidc=awaitgetOidc();if( !oidc.isUserLoggedIn ){thrownewError("We made a logic error: The user should be logged in at this point"); }config.headers.Authorization =`Bearer ${oidc.getTokens().accessToken}`;return config;});exportconstapi:Api= {getTodo: ()=>axiosInstance.get("/todo").then(response =>response.data),addTodo: todo =>axiosInstance.post("/todo", todo).then(response =>response.data)};
Using your REST API client in your REACT components:
This example is purposefully very basic to minimize noise but in your App you should consider using tRPC (if you have JS backend) and TanStack Query.
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:
src/oidc.ts
import { createOidcBackend } from"oidc-spa/backend";import { z } from"zod";import { HTTPException } from"hono/http-exception";exportasyncfunctioncreateDecodeAccessToken() {constoidcIssuerUri=process.env.OIDC_ISSUERif (oidcIssuerUri ===undefined) {thrownewError("OIDC_ISSUER must be defined in the environment variables") }const { verifyAndDecodeAccessToken } =awaitcreateOidcBackend({ 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() }) });functiondecodeAccessToken(params: { authorizationHeaderValue:string|undefined; requiredRole?:string; }) {const { authorizationHeaderValue,requiredRole } = params;if( authorizationHeaderValue ===undefined ){thrownewHTTPException(401); }constresult=verifyAndDecodeAccessToken({ accessToken:authorizationHeaderValue.replace(/^Bearer /,"") });if( !result.isValid ){switch( result.errorCase ){case"does not respect schema":thrownewError(`The access token does not respect the schema ${result.errorMessage}`);case"invalid signature":case"expired":thrownewHTTPException(401); } }const { decodedAccessToken } = result;if( requiredRole !==undefined&&!decodedAccessToken.ream_access.roles.includes(requiredRole) ){thrownewHTTPException(401); }return decodedAccessToken; }return { decodeAccessToken };}
Then you can enforce that some endpoints of your API requires the user to be authenticated, in this example we use Hono: