@azure/msal-browser
Version:
Microsoft Authentication Library for js
459 lines (456 loc) • 26.9 kB
JavaScript
/*! @azure/msal-browser v2.28.1 2022-08-01 */
'use strict';
import { __extends, __awaiter, __generator, __rest, __assign } from '../_virtual/_tslib.js';
import { PerformanceEvents, TimeUtils, ScopeSet, ClientAuthError, IdTokenEntity, Constants, AccessTokenEntity, AuthenticationScheme, PopTokenGenerator, AccountEntity, AuthorityType, AuthToken, UrlString, OIDC_DEFAULT_SCOPES, PromptValue } from '@azure/msal-common';
import { BaseInteractionClient } from './BaseInteractionClient.js';
import { TemporaryCacheKeys, NativeExtensionMethod, NativeConstants, ApiId } from '../utils/BrowserConstants.js';
import { NativeAuthError } from '../error/NativeAuthError.js';
import { BrowserAuthError } from '../error/BrowserAuthError.js';
import { SilentCacheClient } from './SilentCacheClient.js';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var NativeInteractionClient = /** @class */ (function (_super) {
__extends(NativeInteractionClient, _super);
function NativeInteractionClient(config, browserStorage, browserCrypto, logger, eventHandler, navigationClient, apiId, performanceClient, provider, accountId, nativeStorageImpl, correlationId) {
var _this = _super.call(this, config, browserStorage, browserCrypto, logger, eventHandler, navigationClient, performanceClient, provider, correlationId) || this;
_this.apiId = apiId;
_this.accountId = accountId;
_this.nativeMessageHandler = provider;
_this.nativeStorageManager = nativeStorageImpl;
_this.silentCacheClient = new SilentCacheClient(config, _this.nativeStorageManager, browserCrypto, logger, eventHandler, navigationClient, performanceClient, provider, correlationId);
return _this;
}
/**
* Acquire token from native platform via browser extension
* @param request
*/
NativeInteractionClient.prototype.acquireToken = function (request) {
return __awaiter(this, void 0, void 0, function () {
var nativeATMeasurement, reqTimestamp, nativeRequest, result, messageBody, response, validatedResponse;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.trace("NativeInteractionClient - acquireToken called.");
nativeATMeasurement = this.performanceClient.startMeasurement(PerformanceEvents.NativeInteractionClientAcquireToken, request.correlationId);
reqTimestamp = TimeUtils.nowSeconds();
return [4 /*yield*/, this.initializeNativeRequest(request)];
case 1:
nativeRequest = _a.sent();
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, this.acquireTokensFromCache(this.accountId, nativeRequest)];
case 3:
result = _a.sent();
nativeATMeasurement.endMeasurement({
success: true,
isNativeBroker: true,
fromCache: true
});
return [2 /*return*/, result];
case 4:
_a.sent();
// continue with a native call for any and all errors
this.logger.info("MSAL internal Cache does not contain tokens, proceed to make a native call");
return [3 /*break*/, 5];
case 5:
messageBody = {
method: NativeExtensionMethod.GetToken,
request: nativeRequest
};
return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
case 6:
response = _a.sent();
validatedResponse = this.validateNativeResponse(response);
return [2 /*return*/, this.handleNativeResponse(validatedResponse, nativeRequest, reqTimestamp)
.then(function (result) {
nativeATMeasurement.endMeasurement({
success: true,
isNativeBroker: true
});
return result;
})
.catch(function (error) {
nativeATMeasurement.endMeasurement({
success: false,
errorCode: error.errorCode,
subErrorCode: error.subError,
isNativeBroker: true
});
throw error;
})];
}
});
});
};
/**
* Creates silent flow request
* @param request
* @param cachedAccount
* @returns CommonSilentFlowRequest
*/
NativeInteractionClient.prototype.createSilentCacheRequest = function (request, cachedAccount) {
return {
authority: request.authority,
correlationId: this.correlationId,
scopes: ScopeSet.fromString(request.scope).asArray(),
account: cachedAccount,
forceRefresh: false,
};
};
/**
* Fetches the tokens from the cache if un-expired
* @param nativeAccountId
* @param request
* @returns authenticationResult
*/
NativeInteractionClient.prototype.acquireTokensFromCache = function (nativeAccountId, request) {
return __awaiter(this, void 0, void 0, function () {
var accountEntity, account, silentRequest, result, e_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
accountEntity = this.browserStorage.readAccountFromCacheWithNativeAccountId(nativeAccountId);
if (!accountEntity) {
throw ClientAuthError.createNoAccountFoundError();
}
account = accountEntity.getAccountInfo();
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
silentRequest = this.createSilentCacheRequest(request, account);
return [4 /*yield*/, this.silentCacheClient.acquireToken(silentRequest)];
case 2:
result = _a.sent();
return [2 /*return*/, result];
case 3:
e_2 = _a.sent();
throw e_2;
case 4: return [2 /*return*/];
}
});
});
};
/**
* Acquires a token from native platform then redirects to the redirectUri instead of returning the response
* @param request
*/
NativeInteractionClient.prototype.acquireTokenRedirect = function (request) {
return __awaiter(this, void 0, void 0, function () {
var nativeRequest, messageBody, response, e_3, navigationOptions, redirectUri;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.trace("NativeInteractionClient - acquireTokenRedirect called.");
return [4 /*yield*/, this.initializeNativeRequest(request)];
case 1:
nativeRequest = _a.sent();
messageBody = {
method: NativeExtensionMethod.GetToken,
request: nativeRequest
};
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
case 3:
response = _a.sent();
this.validateNativeResponse(response);
return [3 /*break*/, 5];
case 4:
e_3 = _a.sent();
// Only throw fatal errors here to allow application to fallback to regular redirect. Otherwise proceed and the error will be thrown in handleRedirectPromise
if (e_3 instanceof NativeAuthError && e_3.isFatal()) {
throw e_3;
}
return [3 /*break*/, 5];
case 5:
this.browserStorage.setTemporaryCache(TemporaryCacheKeys.NATIVE_REQUEST, JSON.stringify(nativeRequest), true);
navigationOptions = {
apiId: ApiId.acquireTokenRedirect,
timeout: this.config.system.redirectNavigationTimeout,
noHistory: false
};
redirectUri = this.config.auth.navigateToLoginRequestUrl ? window.location.href : this.getRedirectUri(request.redirectUri);
return [4 /*yield*/, this.navigationClient.navigateExternal(redirectUri, navigationOptions)];
case 6:
_a.sent(); // Need to treat this as external to ensure handleRedirectPromise is run again
return [2 /*return*/];
}
});
});
};
/**
* If the previous page called native platform for a token using redirect APIs, send the same request again and return the response
*/
NativeInteractionClient.prototype.handleRedirectPromise = function () {
return __awaiter(this, void 0, void 0, function () {
var cachedRequest, messageBody, reqTimestamp, response, result, e_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.trace("NativeInteractionClient - handleRedirectPromise called.");
if (!this.browserStorage.isInteractionInProgress(true)) {
this.logger.info("handleRedirectPromise called but there is no interaction in progress, returning null.");
return [2 /*return*/, null];
}
cachedRequest = this.browserStorage.getCachedNativeRequest();
if (!cachedRequest) {
this.logger.verbose("NativeInteractionClient - handleRedirectPromise called but there is no cached request, returning null.");
return [2 /*return*/, null];
}
this.browserStorage.removeItem(this.browserStorage.generateCacheKey(TemporaryCacheKeys.NATIVE_REQUEST));
messageBody = {
method: NativeExtensionMethod.GetToken,
request: cachedRequest
};
reqTimestamp = TimeUtils.nowSeconds();
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
this.logger.verbose("NativeInteractionClient - handleRedirectPromise sending message to native broker.");
return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
case 2:
response = _a.sent();
this.validateNativeResponse(response);
result = this.handleNativeResponse(response, cachedRequest, reqTimestamp);
this.browserStorage.setInteractionInProgress(false);
return [2 /*return*/, result];
case 3:
e_4 = _a.sent();
this.browserStorage.setInteractionInProgress(false);
throw e_4;
case 4: return [2 /*return*/];
}
});
});
};
/**
* Logout from native platform via browser extension
* @param request
*/
NativeInteractionClient.prototype.logout = function () {
this.logger.trace("NativeInteractionClient - logout called.");
return Promise.reject("Logout not implemented yet");
};
/**
* Transform response from native platform into AuthenticationResult object which will be returned to the end user
* @param response
* @param request
* @param reqTimestamp
*/
NativeInteractionClient.prototype.handleNativeResponse = function (response, request, reqTimestamp) {
return __awaiter(this, void 0, void 0, function () {
var idTokenObj, authority, authorityPreferredCache, homeAccountIdentifier, accountEntity, responseScopes, accountProperties, uid, tid, responseAccessToken, responseTokenType, _a, popTokenGenerator, shrParameters, mats, result, idTokenEntity, expiresIn, tokenExpirationSeconds, accessTokenEntity;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
this.logger.trace("NativeInteractionClient - handleNativeResponse called.");
if (response.account.id !== request.accountId) {
// User switch in native broker prompt is not supported. All users must first sign in through web flow to ensure server state is in sync
throw NativeAuthError.createUserSwitchError();
}
idTokenObj = new AuthToken(response.id_token || Constants.EMPTY_STRING, this.browserCrypto);
return [4 /*yield*/, this.getDiscoveredAuthority(request.authority)];
case 1:
authority = _b.sent();
authorityPreferredCache = authority.getPreferredCache();
homeAccountIdentifier = AccountEntity.generateHomeAccountId(response.client_info || Constants.EMPTY_STRING, AuthorityType.Default, this.logger, this.browserCrypto, idTokenObj);
accountEntity = AccountEntity.createAccount(response.client_info, homeAccountIdentifier, idTokenObj, undefined, undefined, undefined, authorityPreferredCache, response.account.id);
this.browserStorage.setAccount(accountEntity);
responseScopes = response.scope ? ScopeSet.fromString(response.scope) : ScopeSet.fromString(request.scope);
accountProperties = response.account.properties || {};
uid = accountProperties["UID"] || idTokenObj.claims.oid || idTokenObj.claims.sub || Constants.EMPTY_STRING;
tid = accountProperties["TenantId"] || idTokenObj.claims.tid || Constants.EMPTY_STRING;
responseTokenType = AuthenticationScheme.BEARER;
_a = request.tokenType;
switch (_a) {
case AuthenticationScheme.POP: return [3 /*break*/, 2];
}
return [3 /*break*/, 4];
case 2:
// Set the token type to POP in the response
responseTokenType = AuthenticationScheme.POP;
// Check if native layer returned an SHR token
if (response.shr) {
this.logger.trace("handleNativeServerResponse: SHR is enabled in native layer");
responseAccessToken = response.shr;
return [3 /*break*/, 5];
}
popTokenGenerator = new PopTokenGenerator(this.browserCrypto);
shrParameters = {
resourceRequestMethod: request.resourceRequestMethod,
resourceRequestUri: request.resourceRequestUri,
shrClaims: request.shrClaims,
shrNonce: request.shrNonce
};
/**
* KeyID must be present in the native request from when the PoP key was generated in order for
* PopTokenGenerator to query the full key for signing
*/
if (!request.keyId) {
throw ClientAuthError.createKeyIdMissingError();
}
return [4 /*yield*/, popTokenGenerator.signPopToken(response.access_token, request.keyId, shrParameters)];
case 3:
responseAccessToken = _b.sent();
return [3 /*break*/, 5];
case 4:
{
responseAccessToken = response.access_token;
}
_b.label = 5;
case 5:
mats = this.getMATSFromResponse(response);
result = {
authority: authority.canonicalAuthority,
uniqueId: uid,
tenantId: tid,
scopes: responseScopes.asArray(),
account: accountEntity.getAccountInfo(),
idToken: response.id_token,
idTokenClaims: idTokenObj.claims,
accessToken: responseAccessToken,
fromCache: mats ? this.isResponseFromCache(mats) : false,
expiresOn: new Date(Number(reqTimestamp + response.expires_in) * 1000),
tokenType: responseTokenType,
correlationId: this.correlationId,
state: response.state,
fromNativeBroker: true
};
idTokenEntity = IdTokenEntity.createIdTokenEntity(homeAccountIdentifier, request.authority, response.id_token || Constants.EMPTY_STRING, request.clientId, idTokenObj.claims.tid || Constants.EMPTY_STRING);
this.nativeStorageManager.setIdTokenCredential(idTokenEntity);
expiresIn = (responseTokenType === AuthenticationScheme.POP)
? Constants.SHR_NONCE_VALIDITY
: (typeof response.expires_in === "string"
? parseInt(response.expires_in, 10)
: response.expires_in) || 0;
tokenExpirationSeconds = reqTimestamp + expiresIn;
accessTokenEntity = AccessTokenEntity.createAccessTokenEntity(homeAccountIdentifier, request.authority, responseAccessToken, request.clientId, tid, responseScopes.printScopes(), tokenExpirationSeconds, 0, this.browserCrypto);
this.nativeStorageManager.setAccessTokenCredential(accessTokenEntity);
// Remove any existing cached tokens for this account in browser storage
this.browserStorage.removeAccountContext(accountEntity).catch(function (e) {
_this.logger.error("Error occurred while removing account context from browser storage. " + e);
});
return [2 /*return*/, result];
}
});
});
};
/**
* Validates native platform response before processing
* @param response
*/
NativeInteractionClient.prototype.validateNativeResponse = function (response) {
if (response.hasOwnProperty("access_token") &&
response.hasOwnProperty("id_token") &&
response.hasOwnProperty("client_info") &&
response.hasOwnProperty("account") &&
response.hasOwnProperty("scope") &&
response.hasOwnProperty("expires_in")) {
return response;
}
else {
throw NativeAuthError.createUnexpectedError("Response missing expected properties.");
}
};
/**
* Gets MATS telemetry from native response
* @param response
* @returns
*/
NativeInteractionClient.prototype.getMATSFromResponse = function (response) {
if (response.properties.MATS) {
try {
return JSON.parse(response.properties.MATS);
}
catch (e) {
this.logger.error("NativeInteractionClient - Error parsing MATS telemetry, returning null instead");
}
}
return null;
};
/**
* Returns whether or not response came from native cache
* @param response
* @returns
*/
NativeInteractionClient.prototype.isResponseFromCache = function (mats) {
if (typeof mats.is_cached === "undefined") {
this.logger.verbose("NativeInteractionClient - MATS telemetry does not contain field indicating if response was served from cache. Returning false.");
return false;
}
return !!mats.is_cached;
};
/**
* Translates developer provided request object into NativeRequest object
* @param request
*/
NativeInteractionClient.prototype.initializeNativeRequest = function (request) {
return __awaiter(this, void 0, void 0, function () {
var authority, canonicalAuthority, scopes, remainingProperties, scopeSet, getPrompt, validatedRequest, shrParameters, popTokenGenerator, reqCnfData;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.logger.trace("NativeInteractionClient - initializeNativeRequest called");
authority = request.authority || this.config.auth.authority;
canonicalAuthority = new UrlString(authority);
canonicalAuthority.validateAsUri();
scopes = request.scopes, remainingProperties = __rest(request, ["scopes"]);
scopeSet = new ScopeSet(scopes || []);
scopeSet.appendScopes(OIDC_DEFAULT_SCOPES);
getPrompt = function () {
// If request is silent, prompt is always none
switch (_this.apiId) {
case ApiId.ssoSilent:
case ApiId.acquireTokenSilent_silentFlow:
_this.logger.trace("initializeNativeRequest: silent request sets prompt to none");
return PromptValue.NONE;
}
// Prompt not provided, request may proceed and native broker decides if it needs to prompt
if (!request.prompt) {
_this.logger.trace("initializeNativeRequest: prompt was not provided");
return undefined;
}
// If request is interactive, check if prompt provided is allowed to go directly to native broker
switch (request.prompt) {
case PromptValue.NONE:
case PromptValue.CONSENT:
case PromptValue.LOGIN:
_this.logger.trace("initializeNativeRequest: prompt is compatible with native flow");
return request.prompt;
default:
_this.logger.trace("initializeNativeRequest: prompt = " + request.prompt + " is not compatible with native flow");
throw BrowserAuthError.createNativePromptParameterNotSupportedError();
}
};
validatedRequest = __assign(__assign({}, remainingProperties), { accountId: this.accountId, clientId: this.config.auth.clientId, authority: canonicalAuthority.urlString, scope: scopeSet.printScopes(), redirectUri: this.getRedirectUri(request.redirectUri), prompt: getPrompt(), correlationId: this.correlationId, tokenType: request.authenticationScheme, windowTitleSubstring: document.title, extraParameters: __assign(__assign(__assign({}, request.extraQueryParameters), request.tokenQueryParameters), { telemetry: NativeConstants.MATS_TELEMETRY }), extendedExpiryToken: false // Make this configurable?
});
if (!(request.authenticationScheme === AuthenticationScheme.POP)) return [3 /*break*/, 2];
shrParameters = {
resourceRequestUri: request.resourceRequestUri,
resourceRequestMethod: request.resourceRequestMethod,
shrClaims: request.shrClaims,
shrNonce: request.shrNonce
};
popTokenGenerator = new PopTokenGenerator(this.browserCrypto);
return [4 /*yield*/, popTokenGenerator.generateCnf(shrParameters)];
case 1:
reqCnfData = _a.sent();
// to reduce the URL length, it is recommended to send the hash of the req_cnf instead of the whole string
validatedRequest.reqCnf = reqCnfData.reqCnfHash;
validatedRequest.keyId = reqCnfData.kid;
_a.label = 2;
case 2: return [2 /*return*/, validatedRequest];
}
});
});
};
return NativeInteractionClient;
}(BaseInteractionClient));
export { NativeInteractionClient };
//# sourceMappingURL=NativeInteractionClient.js.map