UNPKG

@graphql-yoga/plugin-jwt

Version:
96 lines (95 loc) 3.5 kB
import { createGraphQLError } from 'graphql-yoga'; import jsonwebtoken from 'jsonwebtoken'; import { JwksClient } from 'jwks-rsa'; const { decode } = jsonwebtoken; export 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 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, }); } }, }; } function unauthorizedError(message, options) { return createGraphQLError(message, { extensions: { http: { status: 401, }, }, ...options, }); } function verify(token, signingKey, options) { return new Promise((resolve, reject) => { jsonwebtoken.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; };