UNPKG

@tidecloak/verify

Version:

A lightweight utility for server-side verification of TideCloak-issued JSON Web Tokens (JWTs).

54 lines (53 loc) 2.72 kB
import { jwtVerify, createLocalJWKSet, createRemoteJWKSet } from "jose"; /** * Verify a TideCloak-issued JWT on the server side using your imported config object. * * @param {object} config - Imported TideCloak configuration (parsed JSON). * @param {string} token - access token to verify. * @param {string[]} [allowedRoles] - Array of Keycloak realm or client roles; user must have at least one. * @returns {Promise<object|null>} - The token payload if valid and role-check passes, otherwise null. */ export async function verifyTideCloakToken(config, token, allowedRoles = []) { var _a, _b, _c; try { // Ensure token is provided if (!token) { throw new Error("No token provided"); } // Ensure config is provided if (!config || Object.keys(config).length === 0) { throw new Error("Could not load TideCloak configuration"); } // Construct issuer URL (ensure slash before 'realms') const baseUrl = config["auth-server-url"]; const sep = baseUrl.endsWith("/") ? "" : "/"; const issuer = `${baseUrl}${sep}realms/${config.realm}`; // Determine JWK set (use local JWKs if provided, otherwise fetch remotely) const jwkSet = config.jwk ? createLocalJWKSet(config.jwk) : createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`)); // Verify token signature and issuer const { payload } = await jwtVerify(token, jwkSet, { issuer }); // Verify authorized party (client) const client = config["resource"]; if (payload.azp !== client) { throw new Error(`AZP mismatch: expected '${config.resource}', got '${payload.azp}'`); } // Gather all user roles from realm and client roles for the specified resource from the config. const realmRoles = ((_a = payload.realm_access) === null || _a === void 0 ? void 0 : _a.roles) || []; const clientRoles = ((_c = (_b = payload.resource_access) === null || _b === void 0 ? void 0 : _b[client]) === null || _c === void 0 ? void 0 : _c.roles) || []; const allRoles = new Set([...realmRoles, ...clientRoles]); // If allowedRoles specified, ensure at least one match if (allowedRoles.length > 0) { const hasAllowed = allowedRoles.some(role => allRoles.has(role)); if (!hasAllowed) { throw new Error(`Role match failed: user roles [${[...allRoles].join(", ")}] do not include any of [${allowedRoles.join(", ")}]`); } } return payload; } catch (err) { console.error("[TideJWT] Token verification failed:", err); return null; } }