/*! @azure/msal-common v14.4.0 2023-11-07 */ 'use strict'; import { isOidcProtocolMode } from '../config/ClientConfiguration.mjs'; import { BaseClient } from './BaseClient.mjs'; import { RequestParameterBuilder } from '../request/RequestParameterBuilder.mjs'; import { AADServerParamKeys, GrantType, AuthenticationScheme, HeaderNames, Errors } from '../utils/Constants.mjs'; import { ResponseHandler } from '../response/ResponseHandler.mjs'; import { PopTokenGenerator } from '../crypto/PopTokenGenerator.mjs'; import { StringUtils } from '../utils/StringUtils.mjs'; import { createClientConfigurationError } from '../error/ClientConfigurationError.mjs'; import { createClientAuthError } from '../error/ClientAuthError.mjs'; import { ServerError } from '../error/ServerError.mjs'; import { TimeUtils } from '../utils/TimeUtils.mjs'; import { UrlString } from '../url/UrlString.mjs'; import { CcsCredentialType } from '../account/CcsCredential.mjs'; import { buildClientInfoFromHomeAccountId } from '../account/ClientInfo.mjs'; import { createInteractionRequiredAuthError, InteractionRequiredAuthError } from '../error/InteractionRequiredAuthError.mjs'; import { PerformanceEvents } from '../telemetry/performance/PerformanceEvent.mjs'; import { invokeAsync, invoke } from '../utils/FunctionWrappers.mjs'; import { tokenRequestEmpty, missingSshJwk } from '../error/ClientConfigurationErrorCodes.mjs'; import { noAccountInSilentRequest } from '../error/ClientAuthErrorCodes.mjs'; import { noTokensFound } from '../error/InteractionRequiredAuthErrorCodes.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * OAuth2.0 refresh token client * @internal */ class RefreshTokenClient extends BaseClient { constructor(configuration, performanceClient) { super(configuration, performanceClient); } async acquireToken(request) { this.performanceClient?.addQueueMeasurement(PerformanceEvents.RefreshTokenClientAcquireToken, request.correlationId); const reqTimestamp = TimeUtils.nowSeconds(); const response = await invokeAsync(this.executeTokenRequest.bind(this), PerformanceEvents.RefreshTokenClientExecuteTokenRequest, this.logger, this.performanceClient, request.correlationId)(request, this.authority); // Retrieve requestId from response headers const requestId = response.headers?.[HeaderNames.X_MS_REQUEST_ID]; const responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger, this.config.serializableCache, this.config.persistencePlugin); responseHandler.validateTokenResponse(response.body); return invokeAsync(responseHandler.handleServerTokenResponse.bind(responseHandler), PerformanceEvents.HandleServerTokenResponse, this.logger, this.performanceClient, request.correlationId)(response.body, this.authority, reqTimestamp, request, undefined, undefined, true, request.forceCache, requestId); } /** * Gets cached refresh token and attaches to request, then calls acquireToken API * @param request */ async acquireTokenByRefreshToken(request) { // Cannot renew token if no request object is given. if (!request) { throw createClientConfigurationError(tokenRequestEmpty); } this.performanceClient?.addQueueMeasurement(PerformanceEvents.RefreshTokenClientAcquireTokenByRefreshToken, request.correlationId); // We currently do not support silent flow for account === null use cases; This will be revisited for confidential flow usecases if (!request.account) { throw createClientAuthError(noAccountInSilentRequest); } // try checking if FOCI is enabled for the given application const isFOCI = this.cacheManager.isAppMetadataFOCI(request.account.environment); // if the app is part of the family, retrive a Family refresh token if present and make a refreshTokenRequest if (isFOCI) { try { return invokeAsync(this.acquireTokenWithCachedRefreshToken.bind(this), PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, true); } catch (e) { const noFamilyRTInCache = e instanceof InteractionRequiredAuthError && e.errorCode === noTokensFound; const clientMismatchErrorWithFamilyRT = e instanceof ServerError && e.errorCode === Errors.INVALID_GRANT_ERROR && e.subError === Errors.CLIENT_MISMATCH_ERROR; // if family Refresh Token (FRT) cache acquisition fails or if client_mismatch error is seen with FRT, reattempt with application Refresh Token (ART) if (noFamilyRTInCache || clientMismatchErrorWithFamilyRT) { return invokeAsync(this.acquireTokenWithCachedRefreshToken.bind(this), PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, false); // throw in all other cases } else { throw e; } } } // fall back to application refresh token acquisition return invokeAsync(this.acquireTokenWithCachedRefreshToken.bind(this), PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, false); } /** * makes a network call to acquire tokens by exchanging RefreshToken available in userCache; throws if refresh token is not cached * @param request */ async acquireTokenWithCachedRefreshToken(request, foci) { this.performanceClient?.addQueueMeasurement(PerformanceEvents.RefreshTokenClientAcquireTokenWithCachedRefreshToken, request.correlationId); // fetches family RT or application RT based on FOCI value const refreshToken = invoke(this.cacheManager.getRefreshToken.bind(this.cacheManager), PerformanceEvents.CacheManagerGetRefreshToken, this.logger, this.performanceClient, request.correlationId)(request.account, foci, undefined, this.performanceClient, request.correlationId); if (!refreshToken) { throw createInteractionRequiredAuthError(noTokensFound); } // attach cached RT size to the current measurement const refreshTokenRequest = { ...request, refreshToken: refreshToken.secret, authenticationScheme: request.authenticationScheme || AuthenticationScheme.BEARER, ccsCredential: { credential: request.account.homeAccountId, type: CcsCredentialType.HOME_ACCOUNT_ID, }, }; return invokeAsync(this.acquireToken.bind(this), PerformanceEvents.RefreshTokenClientAcquireToken, this.logger, this.performanceClient, request.correlationId)(refreshTokenRequest); } /** * Constructs the network message and makes a NW call to the underlying secure token service * @param request * @param authority */ async executeTokenRequest(request, authority) { this.performanceClient?.addQueueMeasurement(PerformanceEvents.RefreshTokenClientExecuteTokenRequest, request.correlationId); const queryParametersString = this.createTokenQueryParameters(request); const endpoint = UrlString.appendQueryString(authority.tokenEndpoint, queryParametersString); const requestBody = await invokeAsync(this.createTokenRequestBody.bind(this), PerformanceEvents.RefreshTokenClientCreateTokenRequestBody, this.logger, this.performanceClient, request.correlationId)(request); const headers = this.createTokenRequestHeaders(request.ccsCredential); const thumbprint = { clientId: request.tokenBodyParameters?.clientId || this.config.authOptions.clientId, authority: authority.canonicalAuthority, scopes: request.scopes, claims: request.claims, authenticationScheme: request.authenticationScheme, resourceRequestMethod: request.resourceRequestMethod, resourceRequestUri: request.resourceRequestUri, shrClaims: request.shrClaims, sshKid: request.sshKid, }; return invokeAsync(this.executePostToTokenEndpoint.bind(this), PerformanceEvents.RefreshTokenClientExecutePostToTokenEndpoint, this.logger, this.performanceClient, request.correlationId)(endpoint, requestBody, headers, thumbprint, request.correlationId, PerformanceEvents.RefreshTokenClientExecutePostToTokenEndpoint); } /** * Helper function to create the token request body * @param request */ async createTokenRequestBody(request) { this.performanceClient?.addQueueMeasurement(PerformanceEvents.RefreshTokenClientCreateTokenRequestBody, request.correlationId); const correlationId = request.correlationId; const parameterBuilder = new RequestParameterBuilder(); parameterBuilder.addClientId(request.tokenBodyParameters?.[AADServerParamKeys.CLIENT_ID] || this.config.authOptions.clientId); if (request.redirectUri) { parameterBuilder.addRedirectUri(request.redirectUri); } parameterBuilder.addScopes(request.scopes, true, this.config.authOptions.authority.options.OIDCOptions?.defaultScopes); parameterBuilder.addGrantType(GrantType.REFRESH_TOKEN_GRANT); parameterBuilder.addClientInfo(); parameterBuilder.addLibraryInfo(this.config.libraryInfo); parameterBuilder.addApplicationTelemetry(this.config.telemetry.application); parameterBuilder.addThrottling(); if (this.serverTelemetryManager && !isOidcProtocolMode(this.config)) { parameterBuilder.addServerTelemetry(this.serverTelemetryManager); } parameterBuilder.addCorrelationId(correlationId); parameterBuilder.addRefreshToken(request.refreshToken); if (this.config.clientCredentials.clientSecret) { parameterBuilder.addClientSecret(this.config.clientCredentials.clientSecret); } if (this.config.clientCredentials.clientAssertion) { const clientAssertion = this.config.clientCredentials.clientAssertion; parameterBuilder.addClientAssertion(clientAssertion.assertion); parameterBuilder.addClientAssertionType(clientAssertion.assertionType); } if (request.authenticationScheme === AuthenticationScheme.POP) { const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils, this.performanceClient); const reqCnfData = await invokeAsync(popTokenGenerator.generateCnf.bind(popTokenGenerator), PerformanceEvents.PopTokenGenerateCnf, this.logger, this.performanceClient, request.correlationId)(request, this.logger); // SPA PoP requires full Base64Url encoded req_cnf string (unhashed) parameterBuilder.addPopToken(reqCnfData.reqCnfString); } else if (request.authenticationScheme === AuthenticationScheme.SSH) { if (request.sshJwk) { parameterBuilder.addSshJwk(request.sshJwk); } else { throw createClientConfigurationError(missingSshJwk); } } if (!StringUtils.isEmptyObj(request.claims) || (this.config.authOptions.clientCapabilities && this.config.authOptions.clientCapabilities.length > 0)) { parameterBuilder.addClaims(request.claims, this.config.authOptions.clientCapabilities); } if (this.config.systemOptions.preventCorsPreflight && request.ccsCredential) { switch (request.ccsCredential.type) { case CcsCredentialType.HOME_ACCOUNT_ID: try { const clientInfo = buildClientInfoFromHomeAccountId(request.ccsCredential.credential); parameterBuilder.addCcsOid(clientInfo); } catch (e) { this.logger.verbose("Could not parse home account ID for CCS Header: " + e); } break; case CcsCredentialType.UPN: parameterBuilder.addCcsUpn(request.ccsCredential.credential); break; } } if (request.tokenBodyParameters) { parameterBuilder.addExtraQueryParameters(request.tokenBodyParameters); } return parameterBuilder.createQueryString(); } } export { RefreshTokenClient }; //# sourceMappingURL=RefreshTokenClient.mjs.map