IntegrateBackends
Fastify integration
Authenticate Fastify routes against Olympus
Fastify + Olympus via the @fastify/oauth2 plugin or custom hooks.
Setup
bun add fastify @fastify/oauth2 @fastify/cookie @fastify/sessionOIDC login
import Fastify from "fastify";
import oauth2 from "@fastify/oauth2";
import cookie from "@fastify/cookie";
import session from "@fastify/session";
const fastify = Fastify();
await fastify.register(cookie);
await fastify.register(session, {
secret: process.env.SESSION_SECRET!,
cookie: { secure: true, sameSite: "lax" },
});
await fastify.register(oauth2, {
name: "olympus",
scope: ["openid", "profile", "email"],
credentials: {
client: {
id: process.env.OLYMPUS_CLIENT_ID!,
secret: process.env.OLYMPUS_CLIENT_SECRET!,
},
auth: {
authorizeHost: process.env.OLYMPUS_ISSUER!,
authorizePath: "/oauth2/auth",
tokenHost: process.env.OLYMPUS_ISSUER!,
tokenPath: "/oauth2/token",
},
},
startRedirectPath: "/auth/login",
callbackUri: process.env.OLYMPUS_REDIRECT_URI!,
pkce: "S256",
});
fastify.get("/auth/callback", async (req, reply) => {
const { token } = await fastify.olympus.getAccessTokenFromAuthorizationCodeFlow(req);
const userinfo = await fetch(`${process.env.OLYMPUS_ISSUER}/userinfo`, {
headers: { authorization: `Bearer ${token.access_token}` },
}).then((r) => r.json());
req.session.user = userinfo;
return reply.redirect("/");
});Token validation middleware
fastify.addHook("onRequest", async (req, reply) => {
if (!req.url.startsWith("/api/")) return;
const auth = req.headers.authorization;
if (!auth?.startsWith("Bearer ")) {
return reply.code(401).send({ error: "missing_token" });
}
const info = await introspect(auth.slice(7));
if (!info.active) {
return reply.code(401).send({ error: "inactive" });
}
req.user = info;
});Schemas
Fastify's JSON Schema validation pairs with Olympus's typed responses:
fastify.get("/api/me", {
schema: {
response: {
200: {
type: "object",
properties: {
sub: { type: "string" },
email: { type: "string" },
},
},
},
},
}, async (req) => req.user);