UNPKG

@azure/msal-browser

Version:
459 lines (456 loc) 26.9 kB
/*! @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