Skip to content

JWKS Guard

JWKS Guard is a library that provides a way of validating JWKS.

Install the JWKS Guard

Terminal window
flowcore component add library/jwks-guard
Or install the JWKS Guard manually

Create a lib/jwks-guard.ts file in your project’s lib folder, and add the following code:

lib/jwks-guard.ts
import { ApiUnauthorizedException } from "@components/api-exceptions"
import bearer from "@elysiajs/bearer"
import Elysia from "elysia"
import { type JWTPayload, createRemoteJWKSet, jwtVerify } from "jose"
export type AuthOptions = {
certificateUrl: string
}
export type FlowcoreJWTPayload = JWTPayload & {
flowcore_user_id: string
email: string
}
export type FlowcoreAuthenticatedUser = {
id: string
email: string
}
/**
* This method is used to create a JWKS guard.
* @param {AuthOptions} options - The options for JWKS guard creation.
* @returns The Elysia instance with the JWKS guard.
* @throws {Error} - If the bearer token is invalid or the JWKS validation fails with a 401 error
*/
export function createJwksGuard(options: AuthOptions) {
const jwks = createRemoteJWKSet(new URL(options.certificateUrl))
const guardRoute = new Elysia()
.use(bearer())
.derive({ as: "scoped" }, async ({ bearer, set }): Promise<{ flowcoreUser: FlowcoreAuthenticatedUser }> => {
const token = bearer as string
const { payload } = await jwtVerify<FlowcoreJWTPayload>(token, jwks, {}).catch(() => ({
payload: null,
}))
if (!payload?.flowcore_user_id) {
throw new ApiUnauthorizedException()
}
return {
flowcoreUser: {
id: payload.flowcore_user_id,
email: payload.email,
},
}
})
return guardRoute
}

Usage

Add a guard to your Elysia app:

server/guards/authenticated-user.ts
import env from "@/env"
import { ApiUnauthorizedException } from "@/lib/api-exceptions"
import { type FlowcoreAuthenticatedUser, createJwksGuard } from "@/lib/jwks-guard"
import { RequestedAccessService } from "@/services/request-access" // Assuming you have a service for request access
import Elysia from "elysia"
/**
* Creates a JWKS guard using the provided certificate URL from the environment.
* This guard is used to validate JWT tokens against a JWKS endpoint.
*/
export const authenticatedFlowcoreUserGuard = createJwksGuard({ certificateUrl: env.JWKS_URL })
/**
* An Elysia plugin that derives an authenticated Flowcore user from the context.
* It checks if the user is a valid Flowcore user and creates a RequestedAccessService instance.
*
* @throws {ApiUnauthorizedException} If the user is not a valid Flowcore user.
* @returns An object containing the flowcoreUser and requestAccessService.
*/
export const authenticatedFlowcoreUser = new Elysia().derive({ as: "scoped" }, (context) => {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const flowcoreUser = (context as any).flowcoreUser as unknown
if (!isFlowcoreUser(flowcoreUser)) {
throw new ApiUnauthorizedException()
}
// Assuming you have a service for request access
const requestAccessService = new RequestedAccessService(flowcoreUser)
return {
flowcoreUser,
requestAccessService,
}
})
/**
* Type guard function to check if a user object is a valid FlowcoreAuthenticatedUser.
*
* @param user - The user object to check.
* @returns A boolean indicating whether the user is a valid FlowcoreAuthenticatedUser.
*/
function isFlowcoreUser(user: unknown): user is FlowcoreAuthenticatedUser {
return typeof user === "object" && user !== null && "id" in user && typeof user.id === "string"
}

then add the guard to your Elysia app:

server/routes/api/index.ts
export default new Elysia({
prefix: "/api",
tags: ["api"],
detail: {
security: [{ bearerAuth: [], oauth2: [] }],
},
})
.use(authenticatedFlowcoreUserGuard)

then for your endpoints

server/routes/api/v1/data-cores/index.ts
export default new Elysia({
prefix: "/v1/data-cores",
tags: ["data-cores"],
detail: {
security: [{ bearerAuth: [], oauth2: [] }],
},
})
.use(authenticatedFlowcoreUser)
.get("/", async () => {
return {
message: "Hello, world!",
}
}, {
beforeHandle: ({ flowcoreUser, RequestedAccessService, params}) =>
requestAccessService.onDataCore(FrnAction.Write, params.organizationId, params.dataCoreId ?? "*"),
params: t.Object({
organizationId: t.String(),
dataCoreId: t.Optional(t.String()),
}),
})