@graphql-yoga/plugin-jwt
Version:
jwt plugin for GraphQL Yoga.
101 lines (100 loc) • 3.73 kB
JavaScript
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;
};
;