UNPKG

@azure/msal-common

Version:
250 lines (247 loc) 15.3 kB
/*! @azure/msal-common v16.6.2 2026-05-19 */ 'use strict'; import { buildClientConfiguration, isOidcProtocolMode } from '../config/ClientConfiguration.mjs'; import { addClientId, addRedirectUri, addScopes, addGrantType, addClientInfo, addLibraryInfo, addApplicationTelemetry, addThrottling, addServerTelemetry, addRefreshToken, addClientSecret, addClientAssertion, addClientAssertionType, addPopToken, addSshJwk, addCcsUpn, addCcsOid, addBrokerParameters, addExtraParameters, instrumentBrokerParams, addClaims } from '../request/RequestParameterBuilder.mjs'; import { mapToQueryString } from '../utils/UrlUtils.mjs'; import { AuthenticationScheme, HeaderNames, INVALID_GRANT_ERROR, CLIENT_MISMATCH_ERROR, GrantType } from '../utils/Constants.mjs'; import { CLIENT_ID } from '../constants/AADServerParamKeys.mjs'; import { ResponseHandler } from '../response/ResponseHandler.mjs'; import { PopTokenGenerator } from '../crypto/PopTokenGenerator.mjs'; import { createClientConfigurationError } from '../error/ClientConfigurationError.mjs'; import { createClientAuthError } from '../error/ClientAuthError.mjs'; import { ServerError } from '../error/ServerError.mjs'; import { nowSeconds, isTokenExpired } 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 { RefreshTokenClientAcquireTokenWithCachedRefreshToken, RefreshTokenClientAcquireToken, RefreshTokenClientExecuteTokenRequest, HandleServerTokenResponse, CacheManagerGetRefreshToken, RefreshTokenClientCreateTokenRequestBody, RefreshTokenClientExecutePostToTokenEndpoint, PopTokenGenerateCnf } from '../telemetry/performance/PerformanceEvents.mjs'; import { invokeAsync, invoke } from '../utils/FunctionWrappers.mjs'; import { getClientAssertion } from '../utils/ClientAssertionUtils.mjs'; import { getRequestThumbprint } from '../network/RequestThumbprint.mjs'; import { createTokenQueryParameters, createTokenRequestHeaders, executePostToTokenEndpoint } from '../protocol/Token.mjs'; import { Logger } from '../logger/Logger.mjs'; import { name, version } from '../packageMetadata.mjs'; import { badToken, noTokensFound, refreshTokenExpired } from '../error/InteractionRequiredAuthErrorCodes.mjs'; import { tokenRequestEmpty, missingSshJwk } from '../error/ClientConfigurationErrorCodes.mjs'; import { noAccountInSilentRequest } from '../error/ClientAuthErrorCodes.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS = 300; // 5 Minutes /** * OAuth2.0 refresh token client * @internal */ class RefreshTokenClient { constructor(configuration, performanceClient) { // Set the configuration this.config = buildClientConfiguration(configuration); // Initialize the logger this.logger = new Logger(this.config.loggerOptions, name, version); // Initialize crypto this.cryptoUtils = this.config.cryptoInterface; // Initialize storage interface this.cacheManager = this.config.storageInterface; // Set the network interface this.networkClient = this.config.networkInterface; // Set TelemetryManager this.serverTelemetryManager = this.config.serverTelemetryManager; // set Authority this.authority = this.config.authOptions.authority; // set performance telemetry client this.performanceClient = performanceClient; } async acquireToken(request, apiId) { const reqTimestamp = nowSeconds(); const response = await invokeAsync(this.executeTokenRequest.bind(this), 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.performanceClient, this.config.serializableCache, this.config.persistencePlugin); responseHandler.validateTokenResponse(response.body, request.correlationId); return invokeAsync(responseHandler.handleServerTokenResponse.bind(responseHandler), HandleServerTokenResponse, this.logger, this.performanceClient, request.correlationId)(response.body, this.authority, reqTimestamp, request, apiId, undefined, undefined, true, request.forceCache, requestId); } /** * Gets cached refresh token and attaches to request, then calls acquireToken API * @param request */ async acquireTokenByRefreshToken(request, apiId) { // Cannot renew token if no request object is given. if (!request) { throw createClientConfigurationError(tokenRequestEmpty); } // 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, request.correlationId); // if the app is part of the family, retrive a Family refresh token if present and make a refreshTokenRequest if (isFOCI) { try { return await invokeAsync(this.acquireTokenWithCachedRefreshToken.bind(this), RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, true, apiId); } catch (e) { const noFamilyRTInCache = e instanceof InteractionRequiredAuthError && e.errorCode === noTokensFound; const clientMismatchErrorWithFamilyRT = e instanceof ServerError && e.errorCode === INVALID_GRANT_ERROR && e.subError === 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), RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, false, apiId); // throw in all other cases } else { throw e; } } } // fall back to application refresh token acquisition return invokeAsync(this.acquireTokenWithCachedRefreshToken.bind(this), RefreshTokenClientAcquireTokenWithCachedRefreshToken, this.logger, this.performanceClient, request.correlationId)(request, false, apiId); } /** * 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, apiId) { // fetches family RT or application RT based on FOCI value const refreshToken = invoke(this.cacheManager.getRefreshToken.bind(this.cacheManager), CacheManagerGetRefreshToken, this.logger, this.performanceClient, request.correlationId)(request.account, foci, request.correlationId, undefined); if (!refreshToken) { throw createInteractionRequiredAuthError(noTokensFound); } if (refreshToken.expiresOn) { const offset = request.refreshTokenExpirationOffsetSeconds || DEFAULT_REFRESH_TOKEN_EXPIRATION_OFFSET_SECONDS; this.performanceClient?.addFields({ cacheRtExpiresOnSeconds: Number(refreshToken.expiresOn), rtOffsetSeconds: offset, }, request.correlationId); if (isTokenExpired(refreshToken.expiresOn, offset)) { throw createInteractionRequiredAuthError(refreshTokenExpired); } } // 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, }, }; try { return await invokeAsync(this.acquireToken.bind(this), RefreshTokenClientAcquireToken, this.logger, this.performanceClient, request.correlationId)(refreshTokenRequest, apiId); } catch (e) { if (e instanceof InteractionRequiredAuthError) { if (e.subError === badToken) { // Remove bad refresh token from cache this.logger.verbose("acquireTokenWithRefreshToken: bad refresh token, removing from cache", request.correlationId); const badRefreshTokenKey = this.cacheManager.generateCredentialKey(refreshToken); this.cacheManager.removeRefreshToken(badRefreshTokenKey, request.correlationId); } } throw e; } } /** * Constructs the network message and makes a NW call to the underlying secure token service * @param request * @param authority */ async executeTokenRequest(request, authority) { const queryParametersString = createTokenQueryParameters(request, this.config.authOptions.clientId, this.config.authOptions.redirectUri, this.performanceClient); const endpoint = UrlString.appendQueryString(authority.tokenEndpoint, queryParametersString); const requestBody = await invokeAsync(this.createTokenRequestBody.bind(this), RefreshTokenClientCreateTokenRequestBody, this.logger, this.performanceClient, request.correlationId)(request); const headers = createTokenRequestHeaders(this.logger, this.config.systemOptions.preventCorsPreflight, request.ccsCredential); const thumbprint = getRequestThumbprint(this.config.authOptions.clientId, request); return invokeAsync(executePostToTokenEndpoint, RefreshTokenClientExecutePostToTokenEndpoint, this.logger, this.performanceClient, request.correlationId)(endpoint, requestBody, headers, thumbprint, request.correlationId, this.cacheManager, this.networkClient, this.logger, this.performanceClient, this.serverTelemetryManager); } /** * Helper function to create the token request body * @param request */ async createTokenRequestBody(request) { const parameters = new Map(); addClientId(parameters, request.embeddedClientId || request.extraParameters?.[CLIENT_ID] || this.config.authOptions.clientId); if (request.redirectUri) { addRedirectUri(parameters, request.redirectUri); } addScopes(parameters, request.scopes, true, this.config.authOptions.authority.options.OIDCOptions?.defaultScopes); addGrantType(parameters, GrantType.REFRESH_TOKEN_GRANT); addClientInfo(parameters); addLibraryInfo(parameters, this.config.libraryInfo); addApplicationTelemetry(parameters, this.config.telemetry.application); addThrottling(parameters); if (this.serverTelemetryManager && !isOidcProtocolMode(this.config)) { addServerTelemetry(parameters, this.serverTelemetryManager); } addRefreshToken(parameters, request.refreshToken); if (this.config.clientCredentials.clientSecret) { addClientSecret(parameters, this.config.clientCredentials.clientSecret); } if (this.config.clientCredentials.clientAssertion) { const clientAssertion = this.config.clientCredentials.clientAssertion; addClientAssertion(parameters, await getClientAssertion(clientAssertion.assertion, this.config.authOptions.clientId, request.resourceRequestUri)); addClientAssertionType(parameters, clientAssertion.assertionType); } if (request.authenticationScheme === AuthenticationScheme.POP) { const popTokenGenerator = new PopTokenGenerator(this.cryptoUtils, this.performanceClient); let reqCnfData; if (!request.popKid) { const generatedReqCnfData = await invokeAsync(popTokenGenerator.generateCnf.bind(popTokenGenerator), PopTokenGenerateCnf, this.logger, this.performanceClient, request.correlationId)(request, this.logger); reqCnfData = generatedReqCnfData.reqCnfString; } else { reqCnfData = this.cryptoUtils.encodeKid(request.popKid); } // SPA PoP requires full Base64Url encoded req_cnf string (unhashed) addPopToken(parameters, reqCnfData); } else if (request.authenticationScheme === AuthenticationScheme.SSH) { if (request.sshJwk) { addSshJwk(parameters, request.sshJwk); } else { throw createClientConfigurationError(missingSshJwk); } } if (this.config.systemOptions.preventCorsPreflight && request.ccsCredential) { switch (request.ccsCredential.type) { case CcsCredentialType.HOME_ACCOUNT_ID: try { const clientInfo = buildClientInfoFromHomeAccountId(request.ccsCredential.credential); addCcsOid(parameters, clientInfo); } catch (e) { this.logger.verbose(`Could not parse home account ID for CCS Header: '${e}'`, request.correlationId); } break; case CcsCredentialType.UPN: addCcsUpn(parameters, request.ccsCredential.credential); break; } } if (request.embeddedClientId) { addBrokerParameters(parameters, this.config.authOptions.clientId, this.config.authOptions.redirectUri); } if (request.extraParameters) { addExtraParameters(parameters, { ...request.extraParameters, }); } instrumentBrokerParams(parameters, request.correlationId, this.performanceClient); addClaims(parameters, request.claims, this.config.authOptions.clientCapabilities, request.skipBrokerClaims); return mapToQueryString(parameters); } } export { RefreshTokenClient }; //# sourceMappingURL=RefreshTokenClient.mjs.map