123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. /* eslint-disable complexity, max-statements */
  2. var http = require('./http');
  3. var util = require('./util');
  4. var oauthUtil = require('./oauthUtil');
  5. var Q = require('q');
  6. var sdkCrypto = require('./crypto');
  7. var AuthSdkError = require('./errors/AuthSdkError');
  8. var OAuthError = require('./errors/OAuthError');
  9. var config = require('./config');
  10. var cookies = require('./cookies');
  11. function decodeToken(token) {
  12. var jwt = token.split('.');
  13. var decodedToken;
  14. try {
  15. decodedToken = {
  16. header: JSON.parse(util.base64UrlToString(jwt[0])),
  17. payload: JSON.parse(util.base64UrlToString(jwt[1])),
  18. signature: jwt[2]
  19. };
  20. } catch(e) {
  21. throw new AuthSdkError('Malformed token');
  22. }
  23. return decodedToken;
  24. }
  25. function verifyIdToken(sdk, idToken, options) {
  26. options = options || {};
  27. if (!sdk.features.isTokenVerifySupported()) {
  28. return Q.reject(new AuthSdkError('This browser doesn\'t support crypto.subtle'));
  29. }
  30. function isExpired(jwtExp) {
  31. var expirationTime;
  32. if (options.expirationTime || options.expirationTime === 0) {
  33. expirationTime = options.expirationTime;
  34. } else {
  35. expirationTime = Math.floor(Date.now()/1000);
  36. }
  37. if (jwtExp &&
  38. jwtExp > expirationTime) {
  39. return true;
  40. }
  41. }
  42. function hasAudience(jwtAudience) {
  43. if (!options.audience) {
  44. return true;
  45. }
  46. var audiences = Array.isArray(options.audience) ? options.audience : [options.audience];
  47. var jwtAudiences = Array.isArray(jwtAudience) ? jwtAudience : [jwtAudience];
  48. var ai = audiences.length;
  49. while (ai--) {
  50. var aud = audiences[ai];
  51. if (jwtAudiences.indexOf(aud) !== -1) {
  52. return true;
  53. }
  54. }
  55. }
  56. return oauthUtil.getWellKnown(sdk)
  57. .then(function(res) {
  58. return http.get(sdk, res['jwks_uri']);
  59. })
  60. .then(function(res) {
  61. var key = res.keys[0];
  62. return sdkCrypto.verifyToken(idToken, key);
  63. })
  64. .then(function(res) {
  65. if (!res) {
  66. return false;
  67. }
  68. var jwt = sdk.token.decode(idToken);
  69. if (isExpired(jwt.payload.exp)) {
  70. return false;
  71. }
  72. if (!hasAudience(jwt.payload.aud)) {
  73. return false;
  74. }
  75. if (options.issuer &&
  76. options.issuer !== jwt.payload.iss) {
  77. return false;
  78. }
  79. return true;
  80. });
  81. }
  82. function verifyToken(sdk, token, nonce, ignoreSignature) {
  83. return new Q()
  84. .then(function() {
  85. if (!token || !token.idToken) {
  86. throw new AuthSdkError('Only idTokens may be verified');
  87. }
  88. var jwt = decodeToken(token.idToken);
  89. // Standard claim validation
  90. oauthUtil.validateClaims(sdk, jwt.payload, token.clientId, token.issuer, nonce);
  91. // If the browser doesn't support native crypto or we choose not
  92. // to verify the signature, bail early
  93. if (ignoreSignature || !sdk.features.isTokenVerifySupported()) {
  94. return token;
  95. }
  96. return oauthUtil.getKey(sdk, token.issuer, jwt.header.kid)
  97. .then(function(key) {
  98. return sdkCrypto.verifyToken(token.idToken, key);
  99. })
  100. .then(function(valid) {
  101. if (!valid) {
  102. throw new AuthSdkError('The token signature is not valid');
  103. }
  104. return token;
  105. });
  106. });
  107. }
  108. function refreshIdToken(sdk, options) {
  109. options = options || {};
  110. options.display = null;
  111. options.prompt = 'none';
  112. return getToken(sdk, options);
  113. }
  114. function addPostMessageListener(sdk, timeout, state) {
  115. var deferred = Q.defer();
  116. function responseHandler(e) {
  117. if (!e.data ||
  118. e.origin !== sdk.options.url ||
  119. (e.data && util.isString(state) && e.data.state !== state)) {
  120. return;
  121. }
  122. deferred.resolve(e.data);
  123. }
  124. oauthUtil.addListener(window, 'message', responseHandler);
  125. return deferred.promise.timeout(timeout || 120000, new AuthSdkError('OAuth flow timed out'))
  126. .fin(function() {
  127. oauthUtil.removeListener(window, 'message', responseHandler);
  128. });
  129. }
  130. function addFragmentListener(sdk, windowEl, timeout) {
  131. var deferred = Q.defer();
  132. function hashChangeHandler() {
  133. /*
  134. We are only able to access window.location.hash on a window
  135. that has the same domain. A try/catch is necessary because
  136. there's no other way to determine that the popup is in
  137. another domain. When we try to access a window on another
  138. domain, an error is thrown.
  139. */
  140. try {
  141. if (windowEl &&
  142. windowEl.location &&
  143. windowEl.location.hash) {
  144. deferred.resolve(oauthUtil.hashToObject(windowEl.location.hash));
  145. } else if (windowEl && !windowEl.closed) {
  146. setTimeout(hashChangeHandler, 500);
  147. }
  148. } catch (err) {
  149. setTimeout(hashChangeHandler, 500);
  150. }
  151. }
  152. hashChangeHandler();
  153. return deferred.promise.timeout(timeout || 120000, new AuthSdkError('OAuth flow timed out'));
  154. }
  155. function handleOAuthResponse(sdk, oauthParams, res, urls) {
  156. urls = urls || {};
  157. var tokenTypes = oauthParams.responseType;
  158. var scopes = util.clone(oauthParams.scopes);
  159. var clientId = oauthParams.clientId || sdk.options.clientId;
  160. return new Q()
  161. .then(function() {
  162. if (res['error'] || res['error_description']) {
  163. throw new OAuthError(res['error'], res['error_description']);
  164. }
  165. if (res.state !== oauthParams.state) {
  166. throw new AuthSdkError('OAuth flow response state doesn\'t match request state');
  167. }
  168. var tokenDict = {};
  169. if (res['access_token']) {
  170. tokenDict['token'] = {
  171. accessToken: res['access_token'],
  172. expiresAt: Number(res['expires_in']) + Math.floor(Date.now()/1000),
  173. tokenType: res['token_type'],
  174. scopes: scopes,
  175. authorizeUrl: urls.authorizeUrl,
  176. userinfoUrl: urls.userinfoUrl
  177. };
  178. }
  179. if (res['code']) {
  180. tokenDict['code'] = {
  181. authorizationCode: res['code']
  182. };
  183. }
  184. if (res['id_token']) {
  185. var jwt = sdk.token.decode(res['id_token']);
  186. var idToken = {
  187. idToken: res['id_token'],
  188. claims: jwt.payload,
  189. expiresAt: jwt.payload.exp,
  190. scopes: scopes,
  191. authorizeUrl: urls.authorizeUrl,
  192. issuer: urls.issuer,
  193. clientId: clientId
  194. };
  195. return verifyToken(sdk, idToken, oauthParams.nonce, true)
  196. .then(function() {
  197. tokenDict['id_token'] = idToken;
  198. return tokenDict;
  199. });
  200. }
  201. return tokenDict;
  202. })
  203. .then(function(tokenDict) {
  204. if (!Array.isArray(tokenTypes)) {
  205. return tokenDict[tokenTypes];
  206. }
  207. if (!tokenDict['token'] && !tokenDict['id_token']) {
  208. throw new AuthSdkError('Unable to parse OAuth flow response');
  209. }
  210. // Create token array in the order of the responseType array
  211. return tokenTypes.map(function(item) {
  212. return tokenDict[item];
  213. });
  214. });
  215. }
  216. function getDefaultOAuthParams(sdk, oauthOptions) {
  217. oauthOptions = util.clone(oauthOptions) || {};
  218. if (oauthOptions.scope) {
  219. util.deprecate('The param "scope" is equivalent to "scopes". Use "scopes" instead.');
  220. oauthOptions.scopes = oauthOptions.scope;
  221. delete oauthOptions.scope;
  222. }
  223. var defaults = {
  224. clientId: sdk.options.clientId,
  225. redirectUri: sdk.options.redirectUri || window.location.href,
  226. responseType: 'id_token',
  227. responseMode: 'okta_post_message',
  228. state: util.genRandomString(64),
  229. nonce: util.genRandomString(64),
  230. scopes: ['openid', 'email']
  231. };
  232. util.extend(defaults, oauthOptions);
  233. return defaults;
  234. }
  235. function convertOAuthParamsToQueryParams(oauthParams) {
  236. // Quick validation
  237. if (!oauthParams.clientId) {
  238. throw new AuthSdkError('A clientId must be specified in the OktaAuth constructor to get a token');
  239. }
  240. if (util.isString(oauthParams.responseType) && oauthParams.responseType.indexOf(' ') !== -1) {
  241. throw new AuthSdkError('Multiple OAuth responseTypes must be defined as an array');
  242. }
  243. // Convert our params to their actual OAuth equivalents
  244. var oauthQueryParams = util.removeNils({
  245. 'client_id': oauthParams.clientId,
  246. 'redirect_uri': oauthParams.redirectUri,
  247. 'response_type': oauthParams.responseType,
  248. 'response_mode': oauthParams.responseMode,
  249. 'state': oauthParams.state,
  250. 'nonce': oauthParams.nonce,
  251. 'prompt': oauthParams.prompt,
  252. 'display': oauthParams.display,
  253. 'sessionToken': oauthParams.sessionToken,
  254. 'idp': oauthParams.idp,
  255. 'max_age': oauthParams.maxAge
  256. });
  257. if (Array.isArray(oauthQueryParams['response_type'])) {
  258. oauthQueryParams['response_type'] = oauthQueryParams['response_type'].join(' ');
  259. }
  260. if (oauthParams.responseType.indexOf('id_token') !== -1 &&
  261. oauthParams.scopes.indexOf('openid') === -1) {
  262. throw new AuthSdkError('openid scope must be specified in the scopes argument when requesting an id_token');
  263. } else {
  264. oauthQueryParams.scope = oauthParams.scopes.join(' ');
  265. }
  266. return oauthQueryParams;
  267. }
  268. function buildAuthorizeParams(oauthParams) {
  269. var oauthQueryParams = convertOAuthParamsToQueryParams(oauthParams);
  270. return util.toQueryParams(oauthQueryParams);
  271. }
  272. /*
  273. * Retrieve an idToken from an Okta or a third party idp
  274. *
  275. * Two main flows:
  276. *
  277. * 1) Exchange a sessionToken for a token
  278. *
  279. * Required:
  280. * clientId: passed via the OktaAuth constructor or into getToken
  281. * sessionToken: 'yourtoken'
  282. *
  283. * Optional:
  284. * redirectUri: defaults to window.location.href
  285. * scopes: defaults to ['openid', 'email']
  286. *
  287. * Forced:
  288. * prompt: 'none'
  289. * responseMode: 'okta_post_message'
  290. * display: undefined
  291. *
  292. * 2) Get a token from an idp
  293. *
  294. * Required:
  295. * clientId: passed via the OktaAuth constructor or into getToken
  296. *
  297. * Optional:
  298. * redirectUri: defaults to window.location.href
  299. * scopes: defaults to ['openid', 'email']
  300. * idp: defaults to Okta as an idp
  301. * prompt: no default. Pass 'none' to throw an error if user is not signed in
  302. *
  303. * Forced:
  304. * display: 'popup'
  305. *
  306. * Only common optional params shown. Any OAuth parameters not explicitly forced are available to override
  307. *
  308. * @param {Object} oauthOptions
  309. * @param {String} [oauthOptions.clientId] ID of this client
  310. * @param {String} [oauthOptions.redirectUri] URI that the iframe or popup will go to once authenticated
  311. * @param {String[]} [oauthOptions.scopes] OAuth 2.0 scopes to request (openid must be specified)
  312. * @param {String} [oauthOptions.idp] ID of an external IdP to use for user authentication
  313. * @param {String} [oauthOptions.sessionToken] Bootstrap Session Token returned by the Okta Authentication API
  314. * @param {String} [oauthOptions.prompt] Determines whether the Okta login will be displayed on failure.
  315. * Use 'none' to prevent this behavior
  316. *
  317. * @param {Object} options
  318. * @param {Integer} [options.timeout] Time in ms before the flow is automatically terminated. Defaults to 120000
  319. * @param {String} [options.popupTitle] Title dispayed in the popup.
  320. * Defaults to 'External Identity Provider User Authentication'
  321. */
  322. function getToken(sdk, oauthOptions, options) {
  323. oauthOptions = oauthOptions || {};
  324. options = options || {};
  325. // Default OAuth query params
  326. var oauthParams = getDefaultOAuthParams(sdk, oauthOptions);
  327. // Start overriding any options that don't make sense
  328. var sessionTokenOverrides = {
  329. prompt: 'none',
  330. responseMode: 'okta_post_message',
  331. display: null
  332. };
  333. var idpOverrides = {
  334. display: 'popup'
  335. };
  336. if (oauthOptions.sessionToken) {
  337. util.extend(oauthParams, sessionTokenOverrides);
  338. } else if (oauthOptions.idp) {
  339. util.extend(oauthParams, idpOverrides);
  340. }
  341. // Use the query params to build the authorize url
  342. var requestUrl,
  343. urls;
  344. try {
  345. // Get authorizeUrl and issuer
  346. urls = oauthUtil.getOAuthUrls(sdk, oauthParams, options);
  347. requestUrl = urls.authorizeUrl + buildAuthorizeParams(oauthParams);
  348. } catch (e) {
  349. return Q.reject(e);
  350. }
  351. // Determine the flow type
  352. var flowType;
  353. if (oauthParams.sessionToken || oauthParams.display === null) {
  354. flowType = 'IFRAME';
  355. } else if (oauthParams.display === 'popup') {
  356. flowType = 'POPUP';
  357. } else {
  358. flowType = 'IMPLICIT';
  359. }
  360. function getOrigin(url) {
  361. var originRegex = /^(https?\:\/\/)?([^:\/?#]*(?:\:[0-9]+)?)/;
  362. return originRegex.exec(url)[0];
  363. }
  364. // Execute the flow type
  365. switch (flowType) {
  366. case 'IFRAME':
  367. var iframePromise = addPostMessageListener(sdk, options.timeout, oauthParams.state);
  368. var iframeEl = oauthUtil.loadFrame(requestUrl);
  369. return iframePromise
  370. .then(function(res) {
  371. return handleOAuthResponse(sdk, oauthParams, res, urls);
  372. })
  373. .fin(function() {
  374. if (document.body.contains(iframeEl)) {
  375. iframeEl.parentElement.removeChild(iframeEl);
  376. }
  377. });
  378. case 'POPUP': // eslint-disable-line no-case-declarations
  379. var popupPromise;
  380. // Add listener on postMessage before window creation, so
  381. // postMessage isn't triggered before we're listening
  382. if (oauthParams.responseMode === 'okta_post_message') {
  383. if (!sdk.features.isPopupPostMessageSupported()) {
  384. return Q.reject(new AuthSdkError('This browser doesn\'t have full postMessage support'));
  385. }
  386. popupPromise = addPostMessageListener(sdk, options.timeout, oauthParams.state);
  387. }
  388. // Create the window
  389. var windowOptions = {
  390. popupTitle: options.popupTitle
  391. };
  392. var windowEl = oauthUtil.loadPopup(requestUrl, windowOptions);
  393. // Poll until we get a valid hash fragment
  394. if (oauthParams.responseMode === 'fragment') {
  395. var windowOrigin = getOrigin(sdk.idToken.authorize._getLocationHref());
  396. var redirectUriOrigin = getOrigin(oauthParams.redirectUri);
  397. if (windowOrigin !== redirectUriOrigin) {
  398. return Q.reject(new AuthSdkError('Using fragment, the redirectUri origin (' + redirectUriOrigin +
  399. ') must match the origin of this page (' + windowOrigin + ')'));
  400. }
  401. popupPromise = addFragmentListener(sdk, windowEl, options.timeout);
  402. }
  403. // Both postMessage and fragment require a poll to see if the popup closed
  404. var popupDeferred = Q.defer();
  405. function hasClosed(win) { // eslint-disable-line no-inner-declarations
  406. if (win.closed) {
  407. popupDeferred.reject(new AuthSdkError('Unable to parse OAuth flow response'));
  408. }
  409. }
  410. var closePoller = setInterval(function() {
  411. hasClosed(windowEl);
  412. }, 500);
  413. // Proxy the promise results into the deferred
  414. popupPromise
  415. .then(function(res) {
  416. popupDeferred.resolve(res);
  417. })
  418. .fail(function(err) {
  419. popupDeferred.reject(err);
  420. });
  421. return popupDeferred.promise
  422. .then(function(res) {
  423. return handleOAuthResponse(sdk, oauthParams, res, urls);
  424. })
  425. .fin(function() {
  426. if (!windowEl.closed) {
  427. clearInterval(closePoller);
  428. windowEl.close();
  429. }
  430. });
  431. default:
  432. return Q.reject(new AuthSdkError('The full page redirect flow is not supported'));
  433. }
  434. }
  435. function getWithoutPrompt(sdk, oauthOptions, options) {
  436. var oauthParams = util.clone(oauthOptions) || {};
  437. util.extend(oauthParams, {
  438. prompt: 'none',
  439. responseMode: 'okta_post_message',
  440. display: null
  441. });
  442. return getToken(sdk, oauthParams, options);
  443. }
  444. function getWithPopup(sdk, oauthOptions, options) {
  445. var oauthParams = util.clone(oauthOptions) || {};
  446. util.extend(oauthParams, {
  447. display: 'popup'
  448. });
  449. return getToken(sdk, oauthParams, options);
  450. }
  451. function getWithRedirect(sdk, oauthOptions, options) {
  452. oauthOptions = util.clone(oauthOptions) || {};
  453. var oauthParams = getDefaultOAuthParams(sdk, oauthOptions);
  454. // If the user didn't specify a responseMode
  455. if (!oauthOptions.responseMode) {
  456. // And it's only an auth code request (responseType could be an array)
  457. var respType = oauthParams.responseType;
  458. if (respType.indexOf('code') !== -1 &&
  459. (util.isString(respType) || (Array.isArray(respType) && respType.length === 1))) {
  460. // Default the responseMode to query
  461. util.extend(oauthParams, {
  462. responseMode: 'query'
  463. });
  464. // Otherwise, default to fragment
  465. } else {
  466. util.extend(oauthParams, {
  467. responseMode: 'fragment'
  468. });
  469. }
  470. }
  471. var urls = oauthUtil.getOAuthUrls(sdk, oauthParams, options);
  472. var requestUrl = urls.authorizeUrl + buildAuthorizeParams(oauthParams);
  473. // Set session cookie to store the oauthParams
  474. cookies.setCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME, JSON.stringify({
  475. responseType: oauthParams.responseType,
  476. state: oauthParams.state,
  477. nonce: oauthParams.nonce,
  478. scopes: oauthParams.scopes,
  479. urls: urls
  480. }));
  481. // Set nonce cookie for servers to validate nonce in id_token
  482. cookies.setCookie(config.REDIRECT_NONCE_COOKIE_NAME, oauthParams.nonce);
  483. // Set state cookie for servers to validate state
  484. cookies.setCookie(config.REDIRECT_STATE_COOKIE_NAME, oauthParams.state);
  485. sdk.token.getWithRedirect._setLocation(requestUrl);
  486. }
  487. function refreshToken(sdk, token) {
  488. if (!oauthUtil.isToken(token)) {
  489. return Q.reject(new AuthSdkError('Refresh must be passed a token with ' +
  490. 'an array of scopes and an accessToken or idToken'));
  491. }
  492. var responseType;
  493. if (token.accessToken) {
  494. responseType = 'token';
  495. } else {
  496. responseType = 'id_token';
  497. }
  498. return sdk.token.getWithoutPrompt({
  499. responseType: responseType,
  500. scopes: token.scopes
  501. }, {
  502. authorizeUrl: token.authorizeUrl,
  503. userinfoUrl: token.userinfoUrl,
  504. issuer: token.issuer
  505. });
  506. }
  507. function removeHash(sdk) {
  508. var nativeHistory = sdk.token.parseFromUrl._getHistory();
  509. var nativeDoc = sdk.token.parseFromUrl._getDocument();
  510. var nativeLoc = sdk.token.parseFromUrl._getLocation();
  511. if (nativeHistory && nativeHistory.replaceState) {
  512. nativeHistory.replaceState(null, nativeDoc.title, nativeLoc.pathname + nativeLoc.search);
  513. } else {
  514. nativeLoc.hash = '';
  515. }
  516. }
  517. function parseFromUrl(sdk, url) {
  518. var nativeLoc = sdk.token.parseFromUrl._getLocation();
  519. var hash = nativeLoc.hash;
  520. if (url) {
  521. hash = url.substring(url.indexOf('#'));
  522. }
  523. var oauthParamsCookie = cookies.getCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME);
  524. if (!hash || !oauthParamsCookie) {
  525. return Q.reject(new AuthSdkError('Unable to parse a token from the url'));
  526. }
  527. try {
  528. var oauthParams = JSON.parse(oauthParamsCookie);
  529. var urls = oauthParams.urls;
  530. delete oauthParams.urls;
  531. cookies.deleteCookie(config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME);
  532. } catch(e) {
  533. return Q.reject(new AuthSdkError('Unable to parse the ' +
  534. config.REDIRECT_OAUTH_PARAMS_COOKIE_NAME + ' cookie: ' + e.message));
  535. }
  536. return Q.resolve(oauthUtil.hashToObject(hash))
  537. .then(function(res) {
  538. if (!url) {
  539. // Remove the hash from the url
  540. removeHash(sdk);
  541. }
  542. return handleOAuthResponse(sdk, oauthParams, res, urls);
  543. });
  544. }
  545. function getUserInfo(sdk, accessTokenObject) {
  546. if (!accessTokenObject ||
  547. (!oauthUtil.isToken(accessTokenObject) && !accessTokenObject.accessToken && !accessTokenObject.userinfoUrl)) {
  548. return Q.reject(new AuthSdkError('getUserInfo requires an access token object'));
  549. }
  550. return http.httpRequest(sdk, {
  551. url: accessTokenObject.userinfoUrl,
  552. method: 'GET',
  553. accessToken: accessTokenObject.accessToken
  554. })
  555. .fail(function(err) {
  556. if (err.xhr && (err.xhr.status === 401 || err.xhr.status === 403)) {
  557. var authenticateHeader = err.xhr.getResponseHeader('WWW-Authenticate');
  558. if (authenticateHeader) {
  559. var errorMatches = authenticateHeader.match(/error="(.*?)"/) || [];
  560. var errorDescriptionMatches = authenticateHeader.match(/error_description="(.*?)"/) || [];
  561. var error = errorMatches[1];
  562. var errorDescription = errorDescriptionMatches[1];
  563. if (error && errorDescription) {
  564. err = new OAuthError(error, errorDescription);
  565. }
  566. }
  567. }
  568. throw err;
  569. });
  570. }
  571. module.exports = {
  572. getToken: getToken,
  573. getWithoutPrompt: getWithoutPrompt,
  574. getWithPopup: getWithPopup,
  575. getWithRedirect: getWithRedirect,
  576. parseFromUrl: parseFromUrl,
  577. refreshIdToken: refreshIdToken,
  578. decodeToken: decodeToken,
  579. verifyIdToken: verifyIdToken,
  580. refreshToken: refreshToken,
  581. getUserInfo: getUserInfo,
  582. verifyToken: verifyToken
  583. };