jwt.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * Copyright 2021 Google Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.JwtErrorCode = exports.JwtError = exports.decodeJwt = exports.verifyJwtSignature = exports.EmulatorSignatureVerifier = exports.PublicKeySignatureVerifier = exports.UrlKeyFetcher = exports.JwksFetcher = exports.ALGORITHM_RS256 = void 0;
  20. const validator = require("./validator");
  21. const jwt = require("jsonwebtoken");
  22. const jwks = require("jwks-rsa");
  23. const api_request_1 = require("../utils/api-request");
  24. exports.ALGORITHM_RS256 = 'RS256';
  25. // `jsonwebtoken` converts errors from the `getKey` callback to its own `JsonWebTokenError` type
  26. // and prefixes the error message with the following. Use the prefix to identify errors thrown
  27. // from the key provider callback.
  28. // https://github.com/auth0/node-jsonwebtoken/blob/d71e383862fc735991fd2e759181480f066bf138/verify.js#L96
  29. const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: ';
  30. const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error';
  31. const NO_KID_IN_HEADER_ERROR_MESSAGE = 'no-kid-in-header-error';
  32. const HOUR_IN_SECONDS = 3600;
  33. class JwksFetcher {
  34. constructor(jwksUrl) {
  35. this.publicKeysExpireAt = 0;
  36. if (!validator.isURL(jwksUrl)) {
  37. throw new Error('The provided JWKS URL is not a valid URL.');
  38. }
  39. this.client = jwks({
  40. jwksUri: jwksUrl,
  41. cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 6 hours.
  42. });
  43. }
  44. fetchPublicKeys() {
  45. if (this.shouldRefresh()) {
  46. return this.refresh();
  47. }
  48. return Promise.resolve(this.publicKeys);
  49. }
  50. shouldRefresh() {
  51. return !this.publicKeys || this.publicKeysExpireAt <= Date.now();
  52. }
  53. refresh() {
  54. return this.client.getSigningKeys()
  55. .then((signingKeys) => {
  56. // reset expire at from previous set of keys.
  57. this.publicKeysExpireAt = 0;
  58. const newKeys = signingKeys.reduce((map, signingKey) => {
  59. map[signingKey.kid] = signingKey.getPublicKey();
  60. return map;
  61. }, {});
  62. this.publicKeysExpireAt = Date.now() + (HOUR_IN_SECONDS * 6 * 1000);
  63. this.publicKeys = newKeys;
  64. return newKeys;
  65. }).catch((err) => {
  66. throw new Error(`Error fetching Json Web Keys: ${err.message}`);
  67. });
  68. }
  69. }
  70. exports.JwksFetcher = JwksFetcher;
  71. /**
  72. * Class to fetch public keys from a client certificates URL.
  73. */
  74. class UrlKeyFetcher {
  75. constructor(clientCertUrl, httpAgent) {
  76. this.clientCertUrl = clientCertUrl;
  77. this.httpAgent = httpAgent;
  78. this.publicKeysExpireAt = 0;
  79. if (!validator.isURL(clientCertUrl)) {
  80. throw new Error('The provided public client certificate URL is not a valid URL.');
  81. }
  82. }
  83. /**
  84. * Fetches the public keys for the Google certs.
  85. *
  86. * @returns A promise fulfilled with public keys for the Google certs.
  87. */
  88. fetchPublicKeys() {
  89. if (this.shouldRefresh()) {
  90. return this.refresh();
  91. }
  92. return Promise.resolve(this.publicKeys);
  93. }
  94. /**
  95. * Checks if the cached public keys need to be refreshed.
  96. *
  97. * @returns Whether the keys should be fetched from the client certs url or not.
  98. */
  99. shouldRefresh() {
  100. return !this.publicKeys || this.publicKeysExpireAt <= Date.now();
  101. }
  102. refresh() {
  103. const client = new api_request_1.HttpClient();
  104. const request = {
  105. method: 'GET',
  106. url: this.clientCertUrl,
  107. httpAgent: this.httpAgent,
  108. };
  109. return client.send(request).then((resp) => {
  110. if (!resp.isJson() || resp.data.error) {
  111. // Treat all non-json messages and messages with an 'error' field as
  112. // error responses.
  113. throw new api_request_1.HttpError(resp);
  114. }
  115. // reset expire at from previous set of keys.
  116. this.publicKeysExpireAt = 0;
  117. if (Object.prototype.hasOwnProperty.call(resp.headers, 'cache-control')) {
  118. const cacheControlHeader = resp.headers['cache-control'];
  119. const parts = cacheControlHeader.split(',');
  120. parts.forEach((part) => {
  121. const subParts = part.trim().split('=');
  122. if (subParts[0] === 'max-age') {
  123. const maxAge = +subParts[1];
  124. this.publicKeysExpireAt = Date.now() + (maxAge * 1000);
  125. }
  126. });
  127. }
  128. this.publicKeys = resp.data;
  129. return resp.data;
  130. }).catch((err) => {
  131. if (err instanceof api_request_1.HttpError) {
  132. let errorMessage = 'Error fetching public keys for Google certs: ';
  133. const resp = err.response;
  134. if (resp.isJson() && resp.data.error) {
  135. errorMessage += `${resp.data.error}`;
  136. if (resp.data.error_description) {
  137. errorMessage += ' (' + resp.data.error_description + ')';
  138. }
  139. }
  140. else {
  141. errorMessage += `${resp.text}`;
  142. }
  143. throw new Error(errorMessage);
  144. }
  145. throw err;
  146. });
  147. }
  148. }
  149. exports.UrlKeyFetcher = UrlKeyFetcher;
  150. /**
  151. * Class for verifying JWT signature with a public key.
  152. */
  153. class PublicKeySignatureVerifier {
  154. constructor(keyFetcher) {
  155. this.keyFetcher = keyFetcher;
  156. if (!validator.isNonNullObject(keyFetcher)) {
  157. throw new Error('The provided key fetcher is not an object or null.');
  158. }
  159. }
  160. static withCertificateUrl(clientCertUrl, httpAgent) {
  161. return new PublicKeySignatureVerifier(new UrlKeyFetcher(clientCertUrl, httpAgent));
  162. }
  163. static withJwksUrl(jwksUrl) {
  164. return new PublicKeySignatureVerifier(new JwksFetcher(jwksUrl));
  165. }
  166. verify(token) {
  167. if (!validator.isString(token)) {
  168. return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
  169. }
  170. return verifyJwtSignature(token, getKeyCallback(this.keyFetcher), { algorithms: [exports.ALGORITHM_RS256] })
  171. .catch((error) => {
  172. if (error.code === JwtErrorCode.NO_KID_IN_HEADER) {
  173. // No kid in JWT header. Try with all the public keys.
  174. return this.verifyWithoutKid(token);
  175. }
  176. throw error;
  177. });
  178. }
  179. verifyWithoutKid(token) {
  180. return this.keyFetcher.fetchPublicKeys()
  181. .then(publicKeys => this.verifyWithAllKeys(token, publicKeys));
  182. }
  183. verifyWithAllKeys(token, keys) {
  184. const promises = [];
  185. Object.values(keys).forEach((key) => {
  186. const result = verifyJwtSignature(token, key)
  187. .then(() => true)
  188. .catch((error) => {
  189. if (error.code === JwtErrorCode.TOKEN_EXPIRED) {
  190. throw error;
  191. }
  192. return false;
  193. });
  194. promises.push(result);
  195. });
  196. return Promise.all(promises)
  197. .then((result) => {
  198. if (result.every((r) => r === false)) {
  199. throw new JwtError(JwtErrorCode.INVALID_SIGNATURE, 'Invalid token signature.');
  200. }
  201. });
  202. }
  203. }
  204. exports.PublicKeySignatureVerifier = PublicKeySignatureVerifier;
  205. /**
  206. * Class for verifying unsigned (emulator) JWTs.
  207. */
  208. class EmulatorSignatureVerifier {
  209. verify(token) {
  210. // Signature checks skipped for emulator; no need to fetch public keys.
  211. return verifyJwtSignature(token, undefined, { algorithms: ['none'] });
  212. }
  213. }
  214. exports.EmulatorSignatureVerifier = EmulatorSignatureVerifier;
  215. /**
  216. * Provides a callback to fetch public keys.
  217. *
  218. * @param fetcher - KeyFetcher to fetch the keys from.
  219. * @returns A callback function that can be used to get keys in `jsonwebtoken`.
  220. */
  221. function getKeyCallback(fetcher) {
  222. return (header, callback) => {
  223. if (!header.kid) {
  224. callback(new Error(NO_KID_IN_HEADER_ERROR_MESSAGE));
  225. }
  226. const kid = header.kid || '';
  227. fetcher.fetchPublicKeys().then((publicKeys) => {
  228. if (!Object.prototype.hasOwnProperty.call(publicKeys, kid)) {
  229. callback(new Error(NO_MATCHING_KID_ERROR_MESSAGE));
  230. }
  231. else {
  232. callback(null, publicKeys[kid]);
  233. }
  234. })
  235. .catch(error => {
  236. callback(error);
  237. });
  238. };
  239. }
  240. /**
  241. * Verifies the signature of a JWT using the provided secret or a function to fetch
  242. * the secret or public key.
  243. *
  244. * @param token - The JWT to be verified.
  245. * @param secretOrPublicKey - The secret or a function to fetch the secret or public key.
  246. * @param options - JWT verification options.
  247. * @returns A Promise resolving for a token with a valid signature.
  248. */
  249. function verifyJwtSignature(token, secretOrPublicKey, options) {
  250. if (!validator.isString(token)) {
  251. return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
  252. }
  253. return new Promise((resolve, reject) => {
  254. jwt.verify(token, secretOrPublicKey, options, (error) => {
  255. if (!error) {
  256. return resolve();
  257. }
  258. if (error.name === 'TokenExpiredError') {
  259. return reject(new JwtError(JwtErrorCode.TOKEN_EXPIRED, 'The provided token has expired. Get a fresh token from your ' +
  260. 'client app and try again.'));
  261. }
  262. else if (error.name === 'JsonWebTokenError') {
  263. if (error.message && error.message.includes(JWT_CALLBACK_ERROR_PREFIX)) {
  264. const message = error.message.split(JWT_CALLBACK_ERROR_PREFIX).pop() || 'Error fetching public keys.';
  265. let code = JwtErrorCode.KEY_FETCH_ERROR;
  266. if (message === NO_MATCHING_KID_ERROR_MESSAGE) {
  267. code = JwtErrorCode.NO_MATCHING_KID;
  268. }
  269. else if (message === NO_KID_IN_HEADER_ERROR_MESSAGE) {
  270. code = JwtErrorCode.NO_KID_IN_HEADER;
  271. }
  272. return reject(new JwtError(code, message));
  273. }
  274. }
  275. return reject(new JwtError(JwtErrorCode.INVALID_SIGNATURE, error.message));
  276. });
  277. });
  278. }
  279. exports.verifyJwtSignature = verifyJwtSignature;
  280. /**
  281. * Decodes general purpose Firebase JWTs.
  282. *
  283. * @param jwtToken - JWT token to be decoded.
  284. * @returns Decoded token containing the header and payload.
  285. */
  286. function decodeJwt(jwtToken) {
  287. if (!validator.isString(jwtToken)) {
  288. return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.'));
  289. }
  290. const fullDecodedToken = jwt.decode(jwtToken, {
  291. complete: true,
  292. });
  293. if (!fullDecodedToken) {
  294. return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'Decoding token failed.'));
  295. }
  296. const header = fullDecodedToken?.header;
  297. const payload = fullDecodedToken?.payload;
  298. return Promise.resolve({ header, payload });
  299. }
  300. exports.decodeJwt = decodeJwt;
  301. /**
  302. * Jwt error code structure.
  303. *
  304. * @param code - The error code.
  305. * @param message - The error message.
  306. * @constructor
  307. */
  308. class JwtError extends Error {
  309. constructor(code, message) {
  310. super(message);
  311. this.code = code;
  312. this.message = message;
  313. this.__proto__ = JwtError.prototype;
  314. }
  315. }
  316. exports.JwtError = JwtError;
  317. /**
  318. * JWT error codes.
  319. */
  320. var JwtErrorCode;
  321. (function (JwtErrorCode) {
  322. JwtErrorCode["INVALID_ARGUMENT"] = "invalid-argument";
  323. JwtErrorCode["INVALID_CREDENTIAL"] = "invalid-credential";
  324. JwtErrorCode["TOKEN_EXPIRED"] = "token-expired";
  325. JwtErrorCode["INVALID_SIGNATURE"] = "invalid-token";
  326. JwtErrorCode["NO_MATCHING_KID"] = "no-matching-kid-error";
  327. JwtErrorCode["NO_KID_IN_HEADER"] = "no-kid-error";
  328. JwtErrorCode["KEY_FETCH_ERROR"] = "key-fetch-error";
  329. })(JwtErrorCode || (exports.JwtErrorCode = JwtErrorCode = {}));