import { createError, getHeader, type H3Event } from "h3"; import { useRuntimeConfig } from "#imports"; import { createRemoteJWKSet, jwtVerify, type JWTPayload, type JWTVerifyOptions, } from "jose"; const globalKey = Symbol.for("slipmatz.auth0.jwks"); type GlobalWithJwks = typeof globalThis & { [globalKey]?: ReturnType; }; const getJwks = (issuer: string) => { const globalScope = globalThis as GlobalWithJwks; if (!globalScope[globalKey]) { globalScope[globalKey] = createRemoteJWKSet( new URL(`${issuer}.well-known/jwks.json`) ); } return globalScope[globalKey]!; }; export type Auth0TokenPayload = JWTPayload & { sub: string; scope?: string; permissions?: string[]; }; export const verifyAccessToken = async (token: string): Promise => { const config = useRuntimeConfig(); const domain = config.public.auth0?.domain; if (!domain) { throw createError({ statusCode: 500, statusMessage: "Auth0 domain is not configured.", }); } const issuer = `https://${domain}/`; const jwks = getJwks(issuer); const options: JWTVerifyOptions = { issuer, }; if (config.public.auth0?.audience) { options.audience = config.public.auth0.audience; } const { payload } = await jwtVerify(token, jwks, options); if (!payload.sub) { throw createError({ statusCode: 401, statusMessage: "Invalid access token payload.", }); } return payload as Auth0TokenPayload; }; export const requireAuth0User = async (event: H3Event): Promise => { const header = getHeader(event, "authorization"); if (!header) { throw createError({ statusCode: 401, statusMessage: "Authorization header missing." }); } const match = header.match(/^Bearer\s+(.+)$/i); if (!match) { throw createError({ statusCode: 401, statusMessage: "Invalid authorization format." }); } const token = match[1]; try { return await verifyAccessToken(token); } catch (error) { throw createError({ statusCode: 401, statusMessage: "Access token verification failed." }); } };