@graphql-yoga/plugin-jwt
Version:
jwt plugin for GraphQL Yoga.
135 lines (134 loc) • 5.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useJWT = useJWT;
const tslib_1 = require("tslib");
const jsonwebtoken_1 = tslib_1.__importDefault(require("jsonwebtoken"));
const config_js_1 = require("./config.js");
require("@whatwg-node/server-plugin-cookies");
const graphql_1 = require("graphql");
const utils_js_1 = require("./utils.js");
function useJWT(options) {
let logger;
const normalizedOptions = (0, config_js_1.normalizeConfig)(options);
const payloadByRequest = new WeakMap();
const lookupToken = async (payload) => {
for (const lookupLocation of normalizedOptions.tokenLookupLocations) {
const token = await lookupLocation(payload);
if (token) {
return token;
}
}
return null;
};
const getSigningKey = async (kid) => {
for (const provider of normalizedOptions.singingKeyProviders) {
try {
const key = await provider(kid);
if (key) {
return key;
}
}
catch (e) {
logger.error(`Failed to fetch signing key from signing provided:`, e);
}
}
return null;
};
return {
onYogaInit({ yoga }) {
logger = yoga.logger;
},
async onRequestParse(payload) {
// Try to find token in request, and reject the request if needed.
const lookupResult = await lookupToken(payload);
if (!lookupResult) {
// If token is missing, we can reject the request based on the configuration.
if (normalizedOptions.reject.missingToken) {
logger.debug(`Token is missing in incoming HTTP request, JWT plugin failed to locate.`);
throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
}
return;
}
try {
// Decode the token first, in order to get the key id to use.
let decodedToken;
try {
decodedToken = jsonwebtoken_1.default.decode(lookupResult.token, { complete: true });
}
catch (e) {
logger.warn(`Failed to decode JWT authentication token: `, e);
throw (0, utils_js_1.badRequestError)(`Invalid authentication token provided`);
}
if (!decodedToken) {
logger.warn(`Failed to extract payload from incoming token, please make sure the token is a valid JWT.`);
throw (0, utils_js_1.badRequestError)(`Invalid authentication token provided`);
}
// Fetch the signing key based on the key id.
const signingKey = await getSigningKey(decodedToken?.header.kid);
if (!signingKey) {
logger.warn(`Signing key is not available for the key id: ${decodedToken?.header.kid}. Please make sure signing key providers are configured correctly.`);
throw Error(`Authentication is not available at the moment.`);
}
// Verify the token with the signing key.
const verified = await verify(logger, lookupResult.token, signingKey, normalizedOptions.tokenVerification);
if (!verified) {
logger.debug(`Token failed to verify, JWT plugin failed to authenticate.`);
throw (0, utils_js_1.unauthorizedError)(`Unauthenticated`);
}
if (verified) {
// Link the verified payload with the request (see `onContextBuilding` for the reading part)
payloadByRequest.set(payload.request, {
payload: verified,
token: {
value: lookupResult.token,
prefix: lookupResult.prefix,
},
});
}
}
catch (e) {
// User-facing errors should be handled based on the configuration.
// These errors are handled based on the value of "reject.invalidToken" config.
if (e instanceof graphql_1.GraphQLError) {
if (normalizedOptions.reject.invalidToken) {
throw e;
}
return;
}
// Server/internal errors should be thrown, so they can be handled by the error handler and be masked.
throw e;
}
},
onContextBuilding({ context, extendContext }) {
if (normalizedOptions.extendContextFieldName === null) {
return;
}
if (context.request == null) {
throw new Error('Request is not available on context! Make sure you use this plugin with GraphQL Yoga.');
}
// Get the payload and inject it into the GraphQL context.
const result = payloadByRequest.get(context.request);
if (result && normalizedOptions.extendContextFieldName) {
extendContext({
[normalizedOptions.extendContextFieldName]: {
payload: result.payload,
token: result.token,
},
});
}
},
};
}
function verify(logger, token, signingKey, options) {
return new Promise((resolve, reject) => {
jsonwebtoken_1.default.verify(token, signingKey, options, (err, result) => {
if (err) {
logger.warn(`Failed to verify authentication token: `, err);
reject((0, utils_js_1.unauthorizedError)('Unauthenticated'));
}
else {
resolve(result);
}
});
});
}