msal
Version:
Microsoft Authentication Library for js
842 lines • 117 kB
JavaScript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { __assign, __awaiter, __generator } from "tslib";
import { AccessTokenKey } from "./cache/AccessTokenKey";
import { AccessTokenValue } from "./cache/AccessTokenValue";
import { ServerRequestParameters } from "./ServerRequestParameters";
import { AuthorityType } from "./authority/Authority";
import { ClientInfo } from "./ClientInfo";
import { IdToken } from "./IdToken";
import { AuthCache } from "./cache/AuthCache";
import { Account } from "./Account";
import { ScopeSet } from "./ScopeSet";
import { StringUtils } from "./utils/StringUtils";
import { WindowUtils } from "./utils/WindowUtils";
import { TokenUtils } from "./utils/TokenUtils";
import { TimeUtils } from "./utils/TimeUtils";
import { UrlUtils } from "./utils/UrlUtils";
import { RequestUtils } from "./utils/RequestUtils";
import { ResponseUtils } from "./utils/ResponseUtils";
import { AuthorityFactory } from "./authority/AuthorityFactory";
import { buildConfiguration } from "./Configuration";
import { ClientConfigurationError } from "./error/ClientConfigurationError";
import { AuthError } from "./error/AuthError";
import { ClientAuthError, ClientAuthErrorMessage } from "./error/ClientAuthError";
import { ServerError } from "./error/ServerError";
import { InteractionRequiredAuthError } from "./error/InteractionRequiredAuthError";
import { buildResponseStateOnly } from "./AuthResponse";
import TelemetryManager from "./telemetry/TelemetryManager";
import { API_EVENT_IDENTIFIER } from "./telemetry/ApiEvent";
import { Constants, ServerHashParamKeys, ResponseTypes, TemporaryCacheKeys, PersistentCacheKeys, ErrorCacheKeys, FramePrefix } from "./utils/Constants";
import { CryptoUtils } from "./utils/CryptoUtils";
import { TrustedAuthority } from "./authority/TrustedAuthority";
import { AuthCacheUtils } from "./utils/AuthCacheUtils";
// default authority
var DEFAULT_AUTHORITY = "https://login.microsoftonline.com/common";
/**
* UserAgentApplication class
*
* Object Instance that the developer can use to make loginXX OR acquireTokenXX functions
*/
var UserAgentApplication = /** @class */ (function () {
/**
* @constructor
* Constructor for the UserAgentApplication used to instantiate the UserAgentApplication object
*
* Important attributes in the Configuration object for auth are:
* - clientID: the application ID of your application.
* You can obtain one by registering your application with our Application registration portal : https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview
* - authority: the authority URL for your application.
*
* In Azure AD, authority is a URL indicating the Azure active directory that MSAL uses to obtain tokens.
* It is of the form https://login.microsoftonline.com/<Enter_the_Tenant_Info_Here>.
* If your application supports Accounts in one organizational directory, replace "Enter_the_Tenant_Info_Here" value with the Tenant Id or Tenant name (for example, contoso.microsoft.com).
* If your application supports Accounts in any organizational directory, replace "Enter_the_Tenant_Info_Here" value with organizations.
* If your application supports Accounts in any organizational directory and personal Microsoft accounts, replace "Enter_the_Tenant_Info_Here" value with common.
* To restrict support to Personal Microsoft accounts only, replace "Enter_the_Tenant_Info_Here" value with consumers.
*
*
* In Azure B2C, authority is of the form https://<instance>/tfp/<tenant>/<policyName>/
*
* @param {@link (Configuration:type)} configuration object for the MSAL UserAgentApplication instance
*/
function UserAgentApplication(configuration) {
// callbacks for token/error
this.authResponseCallback = null;
this.tokenReceivedCallback = null;
this.errorReceivedCallback = null;
// Set the Configuration
this.config = buildConfiguration(configuration);
this.logger = this.config.system.logger;
this.clientId = this.config.auth.clientId;
this.inCookie = this.config.cache.storeAuthStateInCookie;
this.telemetryManager = this.getTelemetryManagerFromConfig(this.config.system.telemetry, this.clientId);
TrustedAuthority.setTrustedAuthoritiesFromConfig(this.config.auth.validateAuthority, this.config.auth.knownAuthorities);
AuthorityFactory.saveMetadataFromConfig(this.config.auth.authority, this.config.auth.authorityMetadata);
// if no authority is passed, set the default: "https://login.microsoftonline.com/common"
this.authority = this.config.auth.authority || DEFAULT_AUTHORITY;
// cache keys msal - typescript throws an error if any value other than "localStorage" or "sessionStorage" is passed
this.cacheStorage = new AuthCache(this.clientId, this.config.cache.cacheLocation, this.inCookie);
// Initialize window handling code
if (!window.activeRenewals) {
window.activeRenewals = {};
}
if (!window.renewStates) {
window.renewStates = [];
}
if (!window.callbackMappedToRenewStates) {
window.callbackMappedToRenewStates = {};
}
if (!window.promiseMappedToRenewStates) {
window.promiseMappedToRenewStates = {};
}
window.msal = this;
var urlHash = window.location.hash;
var urlContainsHash = UrlUtils.urlContainsHash(urlHash);
// check if back button is pressed
WindowUtils.checkIfBackButtonIsPressed(this.cacheStorage);
// On the server 302 - Redirect, handle this
if (urlContainsHash && this.cacheStorage.isInteractionInProgress(true)) {
var stateInfo = this.getResponseState(urlHash);
if (stateInfo.method === Constants.interactionTypeRedirect) {
this.handleRedirectAuthenticationResponse(urlHash);
}
}
}
Object.defineProperty(UserAgentApplication.prototype, "authority", {
/**
* Method to manage the authority URL.
*
* @returns {string} authority
*/
get: function () {
return this.authorityInstance.CanonicalAuthority;
},
/**
* setter for the authority URL
* @param {string} authority
*/
// If the developer passes an authority, create an instance
set: function (val) {
this.authorityInstance = AuthorityFactory.CreateInstance(val, this.config.auth.validateAuthority);
},
enumerable: false,
configurable: true
});
/**
* Get the current authority instance from the MSAL configuration object
*
* @returns {@link Authority} authority instance
*/
UserAgentApplication.prototype.getAuthorityInstance = function () {
return this.authorityInstance;
};
UserAgentApplication.prototype.handleRedirectCallback = function (authOrTokenCallback, errorReceivedCallback) {
if (!authOrTokenCallback) {
throw ClientConfigurationError.createInvalidCallbackObjectError(authOrTokenCallback);
}
// Set callbacks
if (errorReceivedCallback) {
this.tokenReceivedCallback = authOrTokenCallback;
this.errorReceivedCallback = errorReceivedCallback;
this.logger.warning("This overload for callback is deprecated - please change the format of the callbacks to a single callback as shown: (err: AuthError, response: AuthResponse).");
}
else {
this.authResponseCallback = authOrTokenCallback;
}
if (this.redirectError) {
this.authErrorHandler(Constants.interactionTypeRedirect, this.redirectError, this.redirectResponse);
}
else if (this.redirectResponse) {
this.authResponseHandler(Constants.interactionTypeRedirect, this.redirectResponse);
}
};
/**
* Public API to verify if the URL contains the hash with known properties
* @param hash
*/
UserAgentApplication.prototype.urlContainsHash = function (hash) {
this.logger.verbose("UrlContainsHash has been called");
return UrlUtils.urlContainsHash(hash);
};
UserAgentApplication.prototype.authResponseHandler = function (interactionType, response, resolve) {
this.logger.verbose("AuthResponseHandler has been called");
this.cacheStorage.setInteractionInProgress(false);
if (interactionType === Constants.interactionTypeRedirect) {
this.logger.verbose("Interaction type is redirect");
if (this.errorReceivedCallback) {
this.logger.verbose("Two callbacks were provided to handleRedirectCallback, calling success callback with response");
this.tokenReceivedCallback(response);
}
else if (this.authResponseCallback) {
this.logger.verbose("One callback was provided to handleRedirectCallback, calling authResponseCallback with response");
this.authResponseCallback(null, response);
}
}
else if (interactionType === Constants.interactionTypePopup) {
this.logger.verbose("Interaction type is popup, resolving");
resolve(response);
}
else {
throw ClientAuthError.createInvalidInteractionTypeError();
}
};
UserAgentApplication.prototype.authErrorHandler = function (interactionType, authErr, response, reject) {
this.logger.verbose("AuthErrorHandler has been called");
// set interaction_status to complete
this.cacheStorage.setInteractionInProgress(false);
if (interactionType === Constants.interactionTypeRedirect) {
this.logger.verbose("Interaction type is redirect");
if (this.errorReceivedCallback) {
this.logger.verbose("Two callbacks were provided to handleRedirectCallback, calling error callback");
this.errorReceivedCallback(authErr, response.accountState);
}
else if (this.authResponseCallback) {
this.logger.verbose("One callback was provided to handleRedirectCallback, calling authResponseCallback with error");
this.authResponseCallback(authErr, response);
}
else {
this.logger.verbose("handleRedirectCallback has not been called and no callbacks are registered, throwing error");
throw authErr;
}
}
else if (interactionType === Constants.interactionTypePopup) {
this.logger.verbose("Interaction type is popup, rejecting");
reject(authErr);
}
else {
throw ClientAuthError.createInvalidInteractionTypeError();
}
};
// #endregion
/**
* Use when initiating the login process by redirecting the user's browser to the authorization endpoint.
* @param {@link (AuthenticationParameters:type)}
*/
UserAgentApplication.prototype.loginRedirect = function (userRequest) {
this.logger.verbose("LoginRedirect has been called");
// validate request
var request = RequestUtils.validateRequest(userRequest, true, this.clientId, Constants.interactionTypeRedirect);
this.acquireTokenInteractive(Constants.interactionTypeRedirect, true, request, null, null);
};
/**
* Use when you want to obtain an access_token for your API by redirecting the user's browser window to the authorization endpoint.
* @param {@link (AuthenticationParameters:type)}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters
*/
UserAgentApplication.prototype.acquireTokenRedirect = function (userRequest) {
this.logger.verbose("AcquireTokenRedirect has been called");
// validate request
var request = RequestUtils.validateRequest(userRequest, false, this.clientId, Constants.interactionTypeRedirect);
this.acquireTokenInteractive(Constants.interactionTypeRedirect, false, request, null, null);
};
/**
* Use when initiating the login process via opening a popup window in the user's browser
*
* @param {@link (AuthenticationParameters:type)}
*
* @returns {Promise.<AuthResponse>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
*/
UserAgentApplication.prototype.loginPopup = function (userRequest) {
var _this = this;
this.logger.verbose("LoginPopup has been called");
// validate request
var request = RequestUtils.validateRequest(userRequest, true, this.clientId, Constants.interactionTypePopup);
var apiEvent = this.telemetryManager.createAndStartApiEvent(request.correlationId, API_EVENT_IDENTIFIER.LoginPopup);
return new Promise(function (resolve, reject) {
_this.acquireTokenInteractive(Constants.interactionTypePopup, true, request, resolve, reject);
})
.then(function (resp) {
_this.logger.verbose("Successfully logged in");
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, true);
return resp;
})
.catch(function (error) {
_this.cacheStorage.resetTempCacheItems(request.state);
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, false, error.errorCode);
throw error;
});
};
/**
* Use when you want to obtain an access_token for your API via opening a popup window in the user's browser
* @param {@link AuthenticationParameters}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters
* @returns {Promise.<AuthResponse>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
*/
UserAgentApplication.prototype.acquireTokenPopup = function (userRequest) {
var _this = this;
this.logger.verbose("AcquireTokenPopup has been called");
// validate request
var request = RequestUtils.validateRequest(userRequest, false, this.clientId, Constants.interactionTypePopup);
var apiEvent = this.telemetryManager.createAndStartApiEvent(request.correlationId, API_EVENT_IDENTIFIER.AcquireTokenPopup);
return new Promise(function (resolve, reject) {
_this.acquireTokenInteractive(Constants.interactionTypePopup, false, request, resolve, reject);
})
.then(function (resp) {
_this.logger.verbose("Successfully acquired token");
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, true);
return resp;
})
.catch(function (error) {
_this.cacheStorage.resetTempCacheItems(request.state);
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, false, error.errorCode);
throw error;
});
};
// #region Acquire Token
/**
* Use when initiating the login process or when you want to obtain an access_token for your API,
* either by redirecting the user's browser window to the authorization endpoint or via opening a popup window in the user's browser.
* @param {@link (AuthenticationParameters:type)}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters
*/
UserAgentApplication.prototype.acquireTokenInteractive = function (interactionType, isLoginCall, request, resolve, reject) {
var _this = this;
this.logger.verbose("AcquireTokenInteractive has been called");
// block the request if made from the hidden iframe
WindowUtils.blockReloadInHiddenIframes();
try {
this.cacheStorage.setInteractionInProgress(true);
}
catch (e) {
// If already in progress, do not proceed
var thrownError = isLoginCall ? ClientAuthError.createLoginInProgressError() : ClientAuthError.createAcquireTokenInProgressError();
var stateOnlyResponse = buildResponseStateOnly(this.getAccountState(request.state));
this.cacheStorage.resetTempCacheItems(request.state);
this.authErrorHandler(interactionType, thrownError, stateOnlyResponse, reject);
return;
}
if (interactionType === Constants.interactionTypeRedirect) {
this.cacheStorage.setItem(TemporaryCacheKeys.REDIRECT_REQUEST, "" + Constants.inProgress + Constants.resourceDelimiter + request.state);
}
// Get the account object if a session exists
var account;
if (request && request.account && !isLoginCall) {
account = request.account;
this.logger.verbose("Account set from request");
}
else {
account = this.getAccount();
this.logger.verbose("Account set from MSAL Cache");
}
// If no session exists, prompt the user to login.
if (!account && !ServerRequestParameters.isSSOParam(request)) {
if (isLoginCall) {
// extract ADAL id_token if exists
var adalIdToken = this.extractADALIdToken();
// silent login if ADAL id_token is retrieved successfully - SSO
if (adalIdToken && !request.scopes) {
this.logger.info("ADAL's idToken exists. Extracting login information from ADAL's idToken");
var tokenRequest = this.buildIDTokenRequest(request);
this.silentLogin = true;
this.acquireTokenSilent(tokenRequest).then(function (response) {
_this.silentLogin = false;
_this.logger.info("Unified cache call is successful");
_this.authResponseHandler(interactionType, response, resolve);
return;
}, function (error) {
_this.silentLogin = false;
_this.logger.error("Error occurred during unified cache ATS: " + error);
// proceed to login since ATS failed
_this.acquireTokenHelper(null, interactionType, isLoginCall, request, resolve, reject);
});
}
// No ADAL token found, proceed to login
else {
this.logger.verbose("Login call but no token found, proceed to login");
this.acquireTokenHelper(null, interactionType, isLoginCall, request, resolve, reject);
}
}
// AcquireToken call, but no account or context given, so throw error
else {
this.logger.verbose("AcquireToken call, no context or account given");
this.logger.info("User login is required");
var stateOnlyResponse = buildResponseStateOnly(this.getAccountState(request.state));
this.cacheStorage.resetTempCacheItems(request.state);
this.authErrorHandler(interactionType, ClientAuthError.createUserLoginRequiredError(), stateOnlyResponse, reject);
return;
}
}
// User session exists
else {
this.logger.verbose("User session exists, login not required");
this.acquireTokenHelper(account, interactionType, isLoginCall, request, resolve, reject);
}
};
/**
* @hidden
* @ignore
* Helper function to acquireToken
*
*/
UserAgentApplication.prototype.acquireTokenHelper = function (account, interactionType, isLoginCall, request, resolve, reject) {
return __awaiter(this, void 0, void 0, function () {
var requestSignature, serverAuthenticationRequest, acquireTokenAuthority, popUpWindow, responseType, loginStartPage, urlNavigate, hash, error_1, navigate, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.verbose("AcquireTokenHelper has been called");
this.logger.verbose("Interaction type: " + interactionType + ". isLoginCall: " + isLoginCall);
requestSignature = request.scopes ? request.scopes.join(" ").toLowerCase() : Constants.oidcScopes.join(" ");
this.logger.verbosePii("Request signature: " + requestSignature);
acquireTokenAuthority = (request && request.authority) ? AuthorityFactory.CreateInstance(request.authority, this.config.auth.validateAuthority, request.authorityMetadata) : this.authorityInstance;
_a.label = 1;
case 1:
_a.trys.push([1, 11, , 12]);
if (!!acquireTokenAuthority.hasCachedMetadata()) return [3 /*break*/, 3];
this.logger.verbose("No cached metadata for authority");
return [4 /*yield*/, AuthorityFactory.saveMetadataFromNetwork(acquireTokenAuthority, this.telemetryManager, request.correlationId)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
this.logger.verbose("Cached metadata found for authority");
_a.label = 4;
case 4:
responseType = isLoginCall ? ResponseTypes.id_token : this.getTokenType(account, request.scopes);
loginStartPage = request.redirectStartPage || window.location.href;
serverAuthenticationRequest = new ServerRequestParameters(acquireTokenAuthority, this.clientId, responseType, this.getRedirectUri(request && request.redirectUri), request.scopes, request.state, request.correlationId);
this.logger.verbose("Finished building server authentication request");
this.updateCacheEntries(serverAuthenticationRequest, account, isLoginCall, loginStartPage);
this.logger.verbose("Updating cache entries");
// populate QueryParameters (sid/login_hint) and any other extraQueryParameters set by the developer
serverAuthenticationRequest.populateQueryParams(account, request);
this.logger.verbose("Query parameters populated from account");
urlNavigate = UrlUtils.createNavigateUrl(serverAuthenticationRequest) + Constants.response_mode_fragment;
// set state in cache
if (interactionType === Constants.interactionTypeRedirect) {
if (!isLoginCall) {
this.cacheStorage.setItem(AuthCache.generateTemporaryCacheKey(TemporaryCacheKeys.STATE_ACQ_TOKEN, request.state), serverAuthenticationRequest.state, this.inCookie);
this.logger.verbose("State cached for redirect");
this.logger.verbosePii("State cached: " + serverAuthenticationRequest.state);
}
else {
this.logger.verbose("Interaction type redirect but login call is true. State not cached");
}
}
else if (interactionType === Constants.interactionTypePopup) {
window.renewStates.push(serverAuthenticationRequest.state);
window.requestType = isLoginCall ? Constants.login : Constants.renewToken;
this.logger.verbose("State saved to window");
this.logger.verbosePii("State saved: " + serverAuthenticationRequest.state);
// Register callback to capture results from server
this.registerCallback(serverAuthenticationRequest.state, requestSignature, resolve, reject);
}
else {
this.logger.verbose("Invalid interaction error. State not cached");
throw ClientAuthError.createInvalidInteractionTypeError();
}
if (!(interactionType === Constants.interactionTypePopup)) return [3 /*break*/, 9];
this.logger.verbose("Interaction type is popup. Generating popup window");
// Generate a popup window
try {
popUpWindow = this.openPopup(urlNavigate, "msal", Constants.popUpWidth, Constants.popUpHeight);
// Push popup window handle onto stack for tracking
WindowUtils.trackPopup(popUpWindow);
}
catch (e) {
this.logger.info(ClientAuthErrorMessage.popUpWindowError.code + ":" + ClientAuthErrorMessage.popUpWindowError.desc);
this.cacheStorage.setItem(ErrorCacheKeys.ERROR, ClientAuthErrorMessage.popUpWindowError.code);
this.cacheStorage.setItem(ErrorCacheKeys.ERROR_DESC, ClientAuthErrorMessage.popUpWindowError.desc);
if (reject) {
reject(ClientAuthError.createPopupWindowError());
return [2 /*return*/];
}
}
if (!popUpWindow) return [3 /*break*/, 8];
_a.label = 5;
case 5:
_a.trys.push([5, 7, , 8]);
return [4 /*yield*/, WindowUtils.monitorPopupForHash(popUpWindow, this.config.system.loadFrameTimeout, urlNavigate, this.logger)];
case 6:
hash = _a.sent();
this.handleAuthenticationResponse(hash);
// Request completed successfully, set to completed
this.cacheStorage.setInteractionInProgress(false);
this.logger.info("Closing popup window");
// TODO: Check how this can be extracted for any framework specific code?
if (this.config.framework.isAngular) {
this.broadcast("msal:popUpHashChanged", hash);
}
WindowUtils.closePopups();
return [3 /*break*/, 8];
case 7:
error_1 = _a.sent();
if (reject) {
reject(error_1);
}
if (this.config.framework.isAngular) {
this.broadcast("msal:popUpClosed", error_1.errorCode + Constants.resourceDelimiter + error_1.errorMessage);
}
else {
// Request failed, set to canceled
this.cacheStorage.setInteractionInProgress(false);
popUpWindow.close();
}
return [3 /*break*/, 8];
case 8: return [3 /*break*/, 10];
case 9:
// If onRedirectNavigate is implemented, invoke it and provide urlNavigate
if (request.onRedirectNavigate) {
this.logger.verbose("Invoking onRedirectNavigate callback");
navigate = request.onRedirectNavigate(urlNavigate);
// Returning false from onRedirectNavigate will stop navigation
if (navigate !== false) {
this.logger.verbose("onRedirectNavigate did not return false, navigating");
this.navigateWindow(urlNavigate);
}
else {
this.logger.verbose("onRedirectNavigate returned false, stopping navigation");
}
}
else {
// Otherwise, perform navigation
this.logger.verbose("Navigating window to urlNavigate");
this.navigateWindow(urlNavigate);
}
_a.label = 10;
case 10: return [3 /*break*/, 12];
case 11:
err_1 = _a.sent();
this.logger.error(err_1);
this.cacheStorage.resetTempCacheItems(request.state);
this.authErrorHandler(interactionType, ClientAuthError.createEndpointResolutionError(err_1.toString), buildResponseStateOnly(request.state), reject);
if (popUpWindow) {
popUpWindow.close();
}
return [3 /*break*/, 12];
case 12: return [2 /*return*/];
}
});
});
};
/**
* API interfacing idToken request when applications already have a session/hint acquired by authorization client applications
* @param request
*/
UserAgentApplication.prototype.ssoSilent = function (request) {
this.logger.verbose("ssoSilent has been called");
// throw an error on an empty request
if (!request) {
throw ClientConfigurationError.createEmptyRequestError();
}
// throw an error on no hints passed
if (!request.sid && !request.loginHint) {
throw ClientConfigurationError.createSsoSilentError();
}
return this.acquireTokenSilent(__assign(__assign({}, request), { scopes: Constants.oidcScopes }));
};
/**
* Use this function to obtain a token before every call to the API / resource provider
*
* MSAL return's a cached token when available
* Or it send's a request to the STS to obtain a new token using a hidden iframe.
*
* @param {@link AuthenticationParameters}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters
* @returns {Promise.<AuthResponse>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
*
*/
UserAgentApplication.prototype.acquireTokenSilent = function (userRequest) {
var _this = this;
this.logger.verbose("AcquireTokenSilent has been called");
// validate the request
var request = RequestUtils.validateRequest(userRequest, false, this.clientId, Constants.interactionTypeSilent);
var apiEvent = this.telemetryManager.createAndStartApiEvent(request.correlationId, API_EVENT_IDENTIFIER.AcquireTokenSilent);
var requestSignature = RequestUtils.createRequestSignature(request);
return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
var scope, account, adalIdToken, responseType, serverAuthenticationRequest, adalIdTokenObject, userContainedClaims, authErr, cacheResultResponse, logMessage, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// block the request if made from the hidden iframe
WindowUtils.blockReloadInHiddenIframes();
scope = request.scopes.join(" ").toLowerCase();
this.logger.verbosePii("Serialized scopes: " + scope);
if (request.account) {
account = request.account;
this.logger.verbose("Account set from request");
}
else {
account = this.getAccount();
this.logger.verbose("Account set from MSAL Cache");
}
adalIdToken = this.cacheStorage.getItem(Constants.adalIdToken);
// In the event of no account being passed in the config, no session id, and no pre-existing adalIdToken, user will need to log in
if (!account && !(request.sid || request.loginHint) && StringUtils.isEmpty(adalIdToken)) {
this.logger.info("User login is required");
// The promise rejects with a UserLoginRequiredError, which should be caught and user should be prompted to log in interactively
return [2 /*return*/, reject(ClientAuthError.createUserLoginRequiredError())];
}
responseType = this.getTokenType(account, request.scopes);
this.logger.verbose("Response type: " + responseType);
serverAuthenticationRequest = new ServerRequestParameters(AuthorityFactory.CreateInstance(request.authority, this.config.auth.validateAuthority, request.authorityMetadata), this.clientId, responseType, this.getRedirectUri(request.redirectUri), request.scopes, request.state, request.correlationId);
this.logger.verbose("Finished building server authentication request");
// populate QueryParameters (sid/login_hint) and any other extraQueryParameters set by the developer
if (ServerRequestParameters.isSSOParam(request) || account) {
serverAuthenticationRequest.populateQueryParams(account, request, null, true);
this.logger.verbose("Query parameters populated from existing SSO or account");
}
// if user didn't pass login_hint/sid and adal's idtoken is present, extract the login_hint from the adalIdToken
else if (!account && !StringUtils.isEmpty(adalIdToken)) {
adalIdTokenObject = TokenUtils.extractIdToken(adalIdToken);
this.logger.verbose("ADAL's idToken exists. Extracting login information from ADAL's idToken to populate query parameters");
serverAuthenticationRequest.populateQueryParams(account, null, adalIdTokenObject, true);
}
else {
this.logger.verbose("No additional query parameters added");
}
userContainedClaims = request.claimsRequest || serverAuthenticationRequest.claimsValue;
// If request.forceRefresh is set to true, force a request for a new token instead of getting it from the cache
if (!userContainedClaims && !request.forceRefresh) {
try {
cacheResultResponse = this.getCachedToken(serverAuthenticationRequest, account);
}
catch (e) {
authErr = e;
}
}
if (!cacheResultResponse) return [3 /*break*/, 1];
this.logger.verbose("Token found in cache lookup");
this.logger.verbosePii("Scopes found: " + JSON.stringify(cacheResultResponse.scopes));
resolve(cacheResultResponse);
return [2 /*return*/, null];
case 1:
if (!authErr) return [3 /*break*/, 2];
this.logger.infoPii(authErr.errorCode + ":" + authErr.errorMessage);
reject(authErr);
return [2 /*return*/, null];
case 2:
logMessage = void 0;
if (userContainedClaims) {
logMessage = "Skipped cache lookup since claims were given";
}
else if (request.forceRefresh) {
logMessage = "Skipped cache lookup since request.forceRefresh option was set to true";
}
else {
logMessage = "No valid token found in cache lookup";
}
this.logger.verbose(logMessage);
// Cache result can return null if cache is empty. In that case, set authority to default value if no authority is passed to the API.
if (!serverAuthenticationRequest.authorityInstance) {
serverAuthenticationRequest.authorityInstance = request.authority ?
AuthorityFactory.CreateInstance(request.authority, this.config.auth.validateAuthority, request.authorityMetadata)
: this.authorityInstance;
}
this.logger.verbosePii("Authority instance: " + serverAuthenticationRequest.authority);
_a.label = 3;
case 3:
_a.trys.push([3, 7, , 8]);
if (!!serverAuthenticationRequest.authorityInstance.hasCachedMetadata()) return [3 /*break*/, 5];
this.logger.verbose("No cached metadata for authority");
return [4 /*yield*/, AuthorityFactory.saveMetadataFromNetwork(serverAuthenticationRequest.authorityInstance, this.telemetryManager, request.correlationId)];
case 4:
_a.sent();
this.logger.verbose("Authority has been updated with endpoint discovery response");
return [3 /*break*/, 6];
case 5:
this.logger.verbose("Cached metadata found for authority");
_a.label = 6;
case 6:
/*
* refresh attempt with iframe
* Already renewing for this scope, callback when we get the token.
*/
if (window.activeRenewals[requestSignature]) {
this.logger.verbose("Renewing token in progress. Registering callback");
// Active renewals contains the state for each renewal.
this.registerCallback(window.activeRenewals[requestSignature], requestSignature, resolve, reject);
}
else {
if (request.scopes && ScopeSet.onlyContainsOidcScopes(request.scopes)) {
/*
* App uses idToken to send to api endpoints
* Default scope is tracked as OIDC scopes to store this token
*/
this.logger.verbose("OpenID Connect scopes only, renewing idToken");
this.silentLogin = true;
this.renewIdToken(requestSignature, resolve, reject, account, serverAuthenticationRequest);
}
else {
// renew access token
this.logger.verbose("Renewing access token");
this.renewToken(requestSignature, resolve, reject, account, serverAuthenticationRequest);
}
}
return [3 /*break*/, 8];
case 7:
err_2 = _a.sent();
this.logger.error(err_2);
reject(ClientAuthError.createEndpointResolutionError(err_2.toString()));
return [2 /*return*/, null];
case 8: return [2 /*return*/];
}
});
}); })
.then(function (res) {
_this.logger.verbose("Successfully acquired token");
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, true);
return res;
})
.catch(function (error) {
_this.cacheStorage.resetTempCacheItems(request.state);
_this.telemetryManager.stopAndFlushApiEvent(request.correlationId, apiEvent, false, error.errorCode);
throw error;
});
};
// #endregion
// #region Popup Window Creation
/**
* @hidden
*
* Configures popup window for login.
*
* @param urlNavigate
* @param title
* @param popUpWidth
* @param popUpHeight
* @ignore
* @hidden
*/
UserAgentApplication.prototype.openPopup = function (urlNavigate, title, popUpWidth, popUpHeight) {
this.logger.verbose("OpenPopup has been called");
try {
/**
* adding winLeft and winTop to account for dual monitor
* using screenLeft and screenTop for IE8 and earlier
*/
var winLeft = window.screenLeft ? window.screenLeft : window.screenX;
var winTop = window.screenTop ? window.screenTop : window.screenY;
/**
* window.innerWidth displays browser window"s height and width excluding toolbars
* using document.documentElement.clientWidth for IE8 and earlier
*/
var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var left = ((width / 2) - (popUpWidth / 2)) + winLeft;
var top_1 = ((height / 2) - (popUpHeight / 2)) + winTop;
// open the window
var popupWindow = window.open(urlNavigate, title, "width=" + popUpWidth + ", height=" + popUpHeight + ", top=" + top_1 + ", left=" + left + ", scrollbars=yes");
if (!popupWindow) {
throw ClientAuthError.createPopupWindowError();
}
if (popupWindow.focus) {
popupWindow.focus();
}
return popupWindow;
}
catch (e) {
this.cacheStorage.setInteractionInProgress(false);
throw ClientAuthError.createPopupWindowError(e.toString());
}
};
// #endregion
// #region Iframe Management
/**
* @hidden
* Calling _loadFrame but with a timeout to signal failure in loadframeStatus. Callbacks are left.
* registered when network errors occur and subsequent token requests for same resource are registered to the pending request.
* @ignore
*/
UserAgentApplication.prototype.loadIframeTimeout = function (urlNavigate, frameName, requestSignature) {
return __awaiter(this, void 0, void 0, function () {
var expectedState, iframe, _a, hash, error_2;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
expectedState = window.activeRenewals[requestSignature];
this.logger.verbosePii("Set loading state to pending for: " + requestSignature + ":" + expectedState);
this.cacheStorage.setItem(AuthCache.generateTemporaryCacheKey(TemporaryCacheKeys.RENEW_STATUS, expectedState), Constants.inProgress);
if (!this.config.system.navigateFrameWait) return [3 /*break*/, 2];
return [4 /*yield*/, WindowUtils.loadFrame(urlNavigate, frameName, this.config.system.navigateFrameWait, this.logger)];
case 1:
_a = _b.sent();
return [3 /*break*/, 3];
case 2:
_a = WindowUtils.loadFrameSync(urlNavigate, frameName, this.logger);
_b.label = 3;
case 3:
iframe = _a;
_b.label = 4;
case 4:
_b.trys.push([4, 6, , 7]);
return [4 /*yield*/, WindowUtils.monitorIframeForHash(iframe.contentWindow, this.config.system.loadFrameTimeout, urlNavigate, this.logger)];
case 5:
hash = _b.sent();
if (hash) {
this.handleAuthenticationResponse(hash);
}
return [3 /*break*/, 7];
case 6:
error_2 = _b.sent();
if (this.cacheStorage.getItem(AuthCache.generateTemporaryCacheKey(TemporaryCacheKeys.RENEW_STATUS, expectedState)) === Constants.inProgress) {
// fail the iframe session if it's in pending state
this.logger.verbose("Loading frame has timed out after: " + (this.config.system.loadFrameTimeout / 1000) + " seconds for scope/authority " + requestSignature + ":" + expectedState);
// Error after timeout
if (expectedState && window.callbackMappedToRenewStates[expectedState]) {
window.callbackMappedToRenewStates[expectedState](null, error_2);
}
this.cacheStorage.removeItem(AuthCache.generateTemporaryCacheKey(TemporaryCacheKeys.RENEW_STATUS, expectedState));
}
WindowUtils.removeHiddenIframe(iframe);
throw error_2;
case 7:
WindowUtils.removeHiddenIframe(iframe);
return [2 /*return*/];
}
});
});
};
// #endregion
// #region General Helpers
/**
* @hidden
* Used to redirect the browser to the STS authorization endpoint
* @param {string} urlNavigate - URL of the authorization endpoint
*/
UserAgentApplication.prototype.navigateWindow = function (urlNavigate, popupWindow) {
// Navigate if valid URL
if (urlNavigate && !StringUtils.isEmpty(urlNavigate)) {
var navigateWindow = popupWindow ? popupWindow : window;
var logMessage = popupWindow ? "Navigated Popup window to:" + urlNavigate : "Navigate to:" + urlNavigate;
this.logger.infoPii(logMessage);
navigateWindow.location.assign(urlNavigate);
}
else {
this.logger.info("Navigate url is empty");
throw AuthError.createUnexpectedError("Navigate url is empty");
}
};
/**
* @hidden
* Used to add the developer requested callback to the array of callbacks for the specified scopes. The updated array is stored on the window object
* @param {string} expectedState - Unique state identifier (guid).
* @param {string} scope - Developer requested permissions. Not all scopes are guaranteed to be included in the access token returned.
* @param {Function} resolve - The resolve function of the promise object.
* @param {Function} reject - The reject function of the promise object.
* @ignore
*/
UserAgentApplication.prototype.registerCallback = function (expectedState, requestSignature, resolve, reject) {
var _this = this;
// track active renewals
window.activeRenewals[requestSignature] = expectedState;
// initialize callbacks mapped array
if (!window.promiseMappedToRenewStates[expectedState]) {
window.promiseMappedToRenewStates[expectedState] = [];
}
// indexing on the current state, push the callback params to callbacks mapped
window.promiseMappedToRenewStates[expectedState].push({ resolve: resolve, reject: reject });
// Store the server response in the current window??
if (!window.callbackMappedToRenewStates[expectedState]) {
window.callbackMappedToRenewStates[expectedState] = function (response, error) {
// reset active renewals
delete window.activeRenewals[requestSignature];
// for all promiseMappedtoRenewStates for a given 'state' - call the reject/resolve with error/token respectively
for (var i = 0; i < window.promiseMappedToRenewStates[expectedSta