/*! @azure/msal-node v2.5.1 2023-11-07 */ 'use strict'; import { Logger, buildStaticAuthorityOptions, ResponseMode, AuthenticationScheme, AuthorizationCodeClient, AuthError, RefreshTokenClient, SilentFlowClient, createClientAuthError, ClientAuthErrorCodes, Constants as Constants$1, StringUtils, OIDC_DEFAULT_SCOPES, ServerTelemetryManager, Authority, AuthorityFactory } from '@azure/msal-common'; import { buildAppConfiguration } from '../config/Configuration.mjs'; import { CryptoProvider } from '../crypto/CryptoProvider.mjs'; import { NodeStorage } from '../cache/NodeStorage.mjs'; import { ApiId, Constants } from '../utils/Constants.mjs'; import { TokenCache } from '../cache/TokenCache.mjs'; import { name, version } from '../packageMetadata.mjs'; import { NodeAuthError } from '../error/NodeAuthError.mjs'; import { UsernamePasswordClient } from './UsernamePasswordClient.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * Base abstract class for all ClientApplications - public and confidential * @public */ class ClientApplication { /** * Constructor for the ClientApplication */ constructor(configuration) { this.config = buildAppConfiguration(configuration); this.cryptoProvider = new CryptoProvider(); this.logger = new Logger(this.config.system.loggerOptions, name, version); this.storage = new NodeStorage(this.logger, this.config.auth.clientId, this.cryptoProvider, buildStaticAuthorityOptions(this.config.auth)); this.tokenCache = new TokenCache(this.storage, this.logger, this.config.cache.cachePlugin); } /** * Creates the URL of the authorization request, letting the user input credentials and consent to the * application. The URL targets the /authorize endpoint of the authority configured in the * application object. * * Once the user inputs their credentials and consents, the authority will send a response to the redirect URI * sent in the request and should contain an authorization code, which can then be used to acquire tokens via * `acquireTokenByCode(AuthorizationCodeRequest)`. */ async getAuthCodeUrl(request) { this.logger.info("getAuthCodeUrl called", request.correlationId); const validRequest = { ...request, ...(await this.initializeBaseRequest(request)), responseMode: request.responseMode || ResponseMode.QUERY, authenticationScheme: AuthenticationScheme.BEARER, }; const authClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, undefined, undefined, request.azureCloudOptions); const authorizationCodeClient = new AuthorizationCodeClient(authClientConfig); this.logger.verbose("Auth code client created", validRequest.correlationId); return authorizationCodeClient.getAuthCodeUrl(validRequest); } /** * Acquires a token by exchanging the Authorization Code received from the first step of OAuth2.0 * Authorization Code flow. * * `getAuthCodeUrl(AuthorizationCodeUrlRequest)` can be used to create the URL for the first step of OAuth2.0 * Authorization Code flow. Ensure that values for redirectUri and scopes in AuthorizationCodeUrlRequest and * AuthorizationCodeRequest are the same. */ async acquireTokenByCode(request, authCodePayLoad) { this.logger.info("acquireTokenByCode called"); if (request.state && authCodePayLoad) { this.logger.info("acquireTokenByCode - validating state"); this.validateState(request.state, authCodePayLoad.state || ""); // eslint-disable-next-line no-param-reassign authCodePayLoad = { ...authCodePayLoad, state: "" }; } const validRequest = { ...request, ...(await this.initializeBaseRequest(request)), authenticationScheme: AuthenticationScheme.BEARER, }; const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenByCode, validRequest.correlationId); try { const authClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions); const authorizationCodeClient = new AuthorizationCodeClient(authClientConfig); this.logger.verbose("Auth code client created", validRequest.correlationId); return authorizationCodeClient.acquireToken(validRequest, authCodePayLoad); } catch (e) { if (e instanceof AuthError) { e.setCorrelationId(validRequest.correlationId); } serverTelemetryManager.cacheFailedRequest(e); throw e; } } /** * Acquires a token by exchanging the refresh token provided for a new set of tokens. * * This API is provided only for scenarios where you would like to migrate from ADAL to MSAL. Otherwise, it is * recommended that you use `acquireTokenSilent()` for silent scenarios. When using `acquireTokenSilent()`, MSAL will * handle the caching and refreshing of tokens automatically. */ async acquireTokenByRefreshToken(request) { this.logger.info("acquireTokenByRefreshToken called", request.correlationId); const validRequest = { ...request, ...(await this.initializeBaseRequest(request)), authenticationScheme: AuthenticationScheme.BEARER, }; const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenByRefreshToken, validRequest.correlationId); try { const refreshTokenClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions); const refreshTokenClient = new RefreshTokenClient(refreshTokenClientConfig); this.logger.verbose("Refresh token client created", validRequest.correlationId); return refreshTokenClient.acquireToken(validRequest); } catch (e) { if (e instanceof AuthError) { e.setCorrelationId(validRequest.correlationId); } serverTelemetryManager.cacheFailedRequest(e); throw e; } } /** * Acquires a token silently when a user specifies the account the token is requested for. * * This API expects the user to provide an account object and looks into the cache to retrieve the token if present. * There is also an optional "forceRefresh" boolean the user can send to bypass the cache for access_token and id_token. * In case the refresh_token is expired or not found, an error is thrown * and the guidance is for the user to call any interactive token acquisition API (eg: `acquireTokenByCode()`). */ async acquireTokenSilent(request) { const validRequest = { ...request, ...(await this.initializeBaseRequest(request)), forceRefresh: request.forceRefresh || false, }; const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenSilent, validRequest.correlationId, validRequest.forceRefresh); try { const silentFlowClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions); const silentFlowClient = new SilentFlowClient(silentFlowClientConfig); this.logger.verbose("Silent flow client created", validRequest.correlationId); return silentFlowClient.acquireToken(validRequest); } catch (e) { if (e instanceof AuthError) { e.setCorrelationId(validRequest.correlationId); } serverTelemetryManager.cacheFailedRequest(e); throw e; } } /** * Acquires tokens with password grant by exchanging client applications username and password for credentials * * The latest OAuth 2.0 Security Best Current Practice disallows the password grant entirely. * More details on this recommendation at https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-3.4 * Microsoft's documentation and recommendations are at: * https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#usernamepassword * * @param request - UsenamePasswordRequest */ async acquireTokenByUsernamePassword(request) { this.logger.info("acquireTokenByUsernamePassword called", request.correlationId); const validRequest = { ...request, ...(await this.initializeBaseRequest(request)), }; const serverTelemetryManager = this.initializeServerTelemetryManager(ApiId.acquireTokenByUsernamePassword, validRequest.correlationId); try { const usernamePasswordClientConfig = await this.buildOauthClientConfiguration(validRequest.authority, validRequest.correlationId, serverTelemetryManager, undefined, request.azureCloudOptions); const usernamePasswordClient = new UsernamePasswordClient(usernamePasswordClientConfig); this.logger.verbose("Username password client created", validRequest.correlationId); return usernamePasswordClient.acquireToken(validRequest); } catch (e) { if (e instanceof AuthError) { e.setCorrelationId(validRequest.correlationId); } serverTelemetryManager.cacheFailedRequest(e); throw e; } } /** * Gets the token cache for the application. */ getTokenCache() { this.logger.info("getTokenCache called"); return this.tokenCache; } /** * Validates OIDC state by comparing the user cached state with the state received from the server. * * This API is provided for scenarios where you would use OAuth2.0 state parameter to mitigate against * CSRF attacks. * For more information about state, visit https://datatracker.ietf.org/doc/html/rfc6819#section-3.6. * @param state * @param cachedState */ validateState(state, cachedState) { if (!state) { throw NodeAuthError.createStateNotFoundError(); } if (state !== cachedState) { throw createClientAuthError(ClientAuthErrorCodes.stateMismatch); } } /** * Returns the logger instance */ getLogger() { return this.logger; } /** * Replaces the default logger set in configurations with new Logger with new configurations * @param logger - Logger instance */ setLogger(logger) { this.logger = logger; } /** * Builds the common configuration to be passed to the common component based on the platform configurarion * @param authority - user passed authority in configuration * @param serverTelemetryManager - initializes servertelemetry if passed */ async buildOauthClientConfiguration(authority, requestCorrelationId, serverTelemetryManager, azureRegionConfiguration, azureCloudOptions) { this.logger.verbose("buildOauthClientConfiguration called", requestCorrelationId); // precedence - azureCloudInstance + tenant >> authority and request >> config const userAzureCloudOptions = azureCloudOptions ? azureCloudOptions : this.config.auth.azureCloudOptions; // using null assertion operator as we ensure that all config values have default values in buildConfiguration() this.logger.verbose(`building oauth client configuration with the authority: ${authority}`, requestCorrelationId); const discoveredAuthority = await this.createAuthority(authority, azureRegionConfiguration, requestCorrelationId, userAzureCloudOptions); serverTelemetryManager?.updateRegionDiscoveryMetadata(discoveredAuthority.regionDiscoveryMetadata); const clientConfiguration = { authOptions: { clientId: this.config.auth.clientId, authority: discoveredAuthority, clientCapabilities: this.config.auth.clientCapabilities, }, loggerOptions: { logLevel: this.config.system.loggerOptions.logLevel, loggerCallback: this.config.system.loggerOptions.loggerCallback, piiLoggingEnabled: this.config.system.loggerOptions.piiLoggingEnabled, correlationId: requestCorrelationId, }, cacheOptions: { claimsBasedCachingEnabled: this.config.cache.claimsBasedCachingEnabled, }, cryptoInterface: this.cryptoProvider, networkInterface: this.config.system.networkClient, storageInterface: this.storage, serverTelemetryManager: serverTelemetryManager, clientCredentials: { clientSecret: this.clientSecret, clientAssertion: this.clientAssertion ? this.getClientAssertion(discoveredAuthority) : undefined, }, libraryInfo: { sku: Constants.MSAL_SKU, version: version, cpu: process.arch || Constants$1.EMPTY_STRING, os: process.platform || Constants$1.EMPTY_STRING, }, telemetry: this.config.telemetry, persistencePlugin: this.config.cache.cachePlugin, serializableCache: this.tokenCache, }; return clientConfiguration; } getClientAssertion(authority) { return { assertion: this.clientAssertion.getJwt(this.cryptoProvider, this.config.auth.clientId, authority.tokenEndpoint), assertionType: Constants.JWT_BEARER_ASSERTION_TYPE, }; } /** * Generates a request with the default scopes & generates a correlationId. * @param authRequest - BaseAuthRequest for initialization */ async initializeBaseRequest(authRequest) { this.logger.verbose("initializeRequestScopes called", authRequest.correlationId); // Default authenticationScheme to Bearer, log that POP isn't supported yet if (authRequest.authenticationScheme && authRequest.authenticationScheme === AuthenticationScheme.POP) { this.logger.verbose("Authentication Scheme 'pop' is not supported yet, setting Authentication Scheme to 'Bearer' for request", authRequest.correlationId); } authRequest.authenticationScheme = AuthenticationScheme.BEARER; // Set requested claims hash if claims-based caching is enabled and claims were requested if (this.config.cache.claimsBasedCachingEnabled && authRequest.claims && // Checks for empty stringified object "{}" which doesn't qualify as requested claims !StringUtils.isEmptyObj(authRequest.claims)) { authRequest.requestedClaimsHash = await this.cryptoProvider.hashString(authRequest.claims); } return { ...authRequest, scopes: [ ...((authRequest && authRequest.scopes) || []), ...OIDC_DEFAULT_SCOPES, ], correlationId: (authRequest && authRequest.correlationId) || this.cryptoProvider.createNewGuid(), authority: authRequest.authority || this.config.auth.authority, }; } /** * Initializes the server telemetry payload * @param apiId - Id for a specific request * @param correlationId - GUID * @param forceRefresh - boolean to indicate network call */ initializeServerTelemetryManager(apiId, correlationId, forceRefresh) { const telemetryPayload = { clientId: this.config.auth.clientId, correlationId: correlationId, apiId: apiId, forceRefresh: forceRefresh || false, }; return new ServerTelemetryManager(telemetryPayload, this.storage); } /** * Create authority instance. If authority not passed in request, default to authority set on the application * object. If no authority set in application object, then default to common authority. * @param authorityString - authority from user configuration */ async createAuthority(authorityString, azureRegionConfiguration, requestCorrelationId, azureCloudOptions) { this.logger.verbose("createAuthority called", requestCorrelationId); // build authority string based on auth params - azureCloudInstance is prioritized if provided const authorityUrl = Authority.generateAuthority(authorityString, azureCloudOptions); const authorityOptions = { protocolMode: this.config.auth.protocolMode, knownAuthorities: this.config.auth.knownAuthorities, cloudDiscoveryMetadata: this.config.auth.cloudDiscoveryMetadata, authorityMetadata: this.config.auth.authorityMetadata, azureRegionConfiguration, skipAuthorityMetadataCache: this.config.auth.skipAuthorityMetadataCache, }; return await AuthorityFactory.createDiscoveredInstance(authorityUrl, this.config.system.networkClient, this.storage, authorityOptions, this.logger); } /** * Clear the cache */ clearCache() { void this.storage.clear(); } } export { ClientApplication }; //# sourceMappingURL=ClientApplication.mjs.map