UNPKG

@graphql-yoga/plugin-jwt

Version:
101 lines (100 loc) 3.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useJWT = void 0; const tslib_1 = require("tslib"); const graphql_yoga_1 = require("graphql-yoga"); const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken")); const jwks_rsa_1 = require("jwks-rsa"); const { decode } = jsonwebtoken_1.default; function useJWT(options) { if (!options.signingKey && !options.jwksUri) { throw new TypeError('You need to provide either a signingKey or a jwksUri'); } if (options.signingKey && options.jwksUri) { throw new TypeError('You need to provide either a signingKey or a jwksUri, not both'); } const { extendContextField = 'jwt', getToken = defaultGetToken } = options; const payloadByRequest = new WeakMap(); let jwksClient; if (options.jwksUri) { jwksClient = new jwks_rsa_1.JwksClient({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri: options.jwksUri, }); } return { async onRequest({ request, serverContext, url }) { const token = await getToken({ request, serverContext, url }); if (token != null) { const signingKey = options.signingKey ?? (await fetchKey(jwksClient, token)); const verified = await verify(token, signingKey, options); if (!verified) { throw unauthorizedError(`Unauthenticated`); } payloadByRequest.set(request, verified); } }, onContextBuilding({ context, extendContext }) { if (context.request == null) { throw new Error('Request is not available on context! Make sure you use this plugin with GraphQL Yoga.'); } const payload = payloadByRequest.get(context.request); if (payload != null) { extendContext({ [extendContextField]: payload, }); } }, }; } exports.useJWT = useJWT; function unauthorizedError(message, options) { return (0, graphql_yoga_1.createGraphQLError)(message, { extensions: { http: { status: 401, }, }, ...options, }); } function verify(token, signingKey, options) { return new Promise((resolve, reject) => { jsonwebtoken_1.default.verify(token, signingKey, { ...options, algorithms: options?.algorithms ?? ['RS256'] }, (err, result) => { if (err) { // Should we expose the error message? Perhaps only in development mode? reject(unauthorizedError('Failed to decode authentication token', { originalError: err, })); } else { resolve(result); } }); }); } async function fetchKey(jwksClient, token) { const decodedToken = decode(token, { complete: true }); if (decodedToken?.header?.kid == null) { throw unauthorizedError(`Failed to decode authentication token. Missing key id.`); } const secret = await jwksClient.getSigningKey(decodedToken.header.kid); const signingKey = secret?.getPublicKey(); if (!signingKey) { throw unauthorizedError(`Failed to decode authentication token. Unknown key id.`); } return signingKey; } const defaultGetToken = ({ request }) => { const header = request.headers.get('authorization'); if (!header) { return; } const [type, token] = header.split(' '); if (type !== 'Bearer') { throw unauthorizedError(`Unsupported token type provided: "${type}"`); } return token; };