@azure/msal-browser
Version:
Microsoft Authentication Library for js
349 lines (327 loc) • 12.5 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
ICrypto,
Logger,
PromptValue,
CommonAuthorizationCodeRequest,
AuthorizationCodeClient,
AuthError,
ProtocolUtils,
IPerformanceClient,
PerformanceEvents,
invokeAsync,
invoke,
} from "@azure/msal-common/browser";
import { StandardInteractionClient } from "./StandardInteractionClient.js";
import { AuthorizationUrlRequest } from "../request/AuthorizationUrlRequest.js";
import { BrowserConfiguration } from "../config/Configuration.js";
import { BrowserCacheManager } from "../cache/BrowserCacheManager.js";
import { EventHandler } from "../event/EventHandler.js";
import { INavigationClient } from "../navigation/INavigationClient.js";
import {
createBrowserAuthError,
BrowserAuthErrorCodes,
} from "../error/BrowserAuthError.js";
import {
InteractionType,
ApiId,
BrowserConstants,
} from "../utils/BrowserConstants.js";
import {
initiateAuthRequest,
monitorIframeForHash,
} from "../interaction_handler/SilentHandler.js";
import { SsoSilentRequest } from "../request/SsoSilentRequest.js";
import { NativeMessageHandler } from "../broker/nativeBroker/NativeMessageHandler.js";
import { NativeInteractionClient } from "./NativeInteractionClient.js";
import { AuthenticationResult } from "../response/AuthenticationResult.js";
import { InteractionHandler } from "../interaction_handler/InteractionHandler.js";
import * as BrowserUtils from "../utils/BrowserUtils.js";
import * as ResponseHandler from "../response/ResponseHandler.js";
export class SilentIframeClient extends StandardInteractionClient {
protected apiId: ApiId;
protected nativeStorage: BrowserCacheManager;
constructor(
config: BrowserConfiguration,
storageImpl: BrowserCacheManager,
browserCrypto: ICrypto,
logger: Logger,
eventHandler: EventHandler,
navigationClient: INavigationClient,
apiId: ApiId,
performanceClient: IPerformanceClient,
nativeStorageImpl: BrowserCacheManager,
nativeMessageHandler?: NativeMessageHandler,
correlationId?: string
) {
super(
config,
storageImpl,
browserCrypto,
logger,
eventHandler,
navigationClient,
performanceClient,
nativeMessageHandler,
correlationId
);
this.apiId = apiId;
this.nativeStorage = nativeStorageImpl;
}
/**
* Acquires a token silently by opening a hidden iframe to the /authorize endpoint with prompt=none or prompt=no_session
* @param request
*/
async acquireToken(
request: SsoSilentRequest
): Promise<AuthenticationResult> {
this.performanceClient.addQueueMeasurement(
PerformanceEvents.SilentIframeClientAcquireToken,
request.correlationId
);
// Check that we have some SSO data
if (
!request.loginHint &&
!request.sid &&
(!request.account || !request.account.username)
) {
this.logger.warning(
"No user hint provided. The authorization server may need more information to complete this request."
);
}
// Check the prompt value
const inputRequest = { ...request };
if (inputRequest.prompt) {
if (
inputRequest.prompt !== PromptValue.NONE &&
inputRequest.prompt !== PromptValue.NO_SESSION
) {
this.logger.warning(
`SilentIframeClient. Replacing invalid prompt ${inputRequest.prompt} with ${PromptValue.NONE}`
);
inputRequest.prompt = PromptValue.NONE;
}
} else {
inputRequest.prompt = PromptValue.NONE;
}
// Create silent request
const silentRequest: AuthorizationUrlRequest = await invokeAsync(
this.initializeAuthorizationRequest.bind(this),
PerformanceEvents.StandardInteractionClientInitializeAuthorizationRequest,
this.logger,
this.performanceClient,
request.correlationId
)(inputRequest, InteractionType.Silent);
BrowserUtils.preconnect(silentRequest.authority);
const serverTelemetryManager = this.initializeServerTelemetryManager(
this.apiId
);
let authClient: AuthorizationCodeClient | undefined;
try {
// Initialize the client
authClient = await invokeAsync(
this.createAuthCodeClient.bind(this),
PerformanceEvents.StandardInteractionClientCreateAuthCodeClient,
this.logger,
this.performanceClient,
request.correlationId
)({
serverTelemetryManager,
requestAuthority: silentRequest.authority,
requestAzureCloudOptions: silentRequest.azureCloudOptions,
requestExtraQueryParameters: silentRequest.extraQueryParameters,
account: silentRequest.account,
});
return await invokeAsync(
this.silentTokenHelper.bind(this),
PerformanceEvents.SilentIframeClientTokenHelper,
this.logger,
this.performanceClient,
request.correlationId
)(authClient, silentRequest);
} catch (e) {
if (e instanceof AuthError) {
(e as AuthError).setCorrelationId(this.correlationId);
serverTelemetryManager.cacheFailedRequest(e);
}
if (
!authClient ||
!(e instanceof AuthError) ||
e.errorCode !== BrowserConstants.INVALID_GRANT_ERROR
) {
throw e;
}
this.performanceClient.addFields(
{
retryError: e.errorCode,
},
this.correlationId
);
const retrySilentRequest: AuthorizationUrlRequest =
await invokeAsync(
this.initializeAuthorizationRequest.bind(this),
PerformanceEvents.StandardInteractionClientInitializeAuthorizationRequest,
this.logger,
this.performanceClient,
request.correlationId
)(inputRequest, InteractionType.Silent);
return await invokeAsync(
this.silentTokenHelper.bind(this),
PerformanceEvents.SilentIframeClientTokenHelper,
this.logger,
this.performanceClient,
this.correlationId
)(authClient, retrySilentRequest);
}
}
/**
* Currently Unsupported
*/
logout(): Promise<void> {
// Synchronous so we must reject
return Promise.reject(
createBrowserAuthError(
BrowserAuthErrorCodes.silentLogoutUnsupported
)
);
}
/**
* Helper which acquires an authorization code silently using a hidden iframe from given url
* using the scopes requested as part of the id, and exchanges the code for a set of OAuth tokens.
* @param navigateUrl
* @param userRequestScopes
*/
protected async silentTokenHelper(
authClient: AuthorizationCodeClient,
silentRequest: AuthorizationUrlRequest
): Promise<AuthenticationResult> {
const correlationId = silentRequest.correlationId;
this.performanceClient.addQueueMeasurement(
PerformanceEvents.SilentIframeClientTokenHelper,
correlationId
);
// Create auth code request and generate PKCE params
const authCodeRequest: CommonAuthorizationCodeRequest =
await invokeAsync(
this.initializeAuthorizationCodeRequest.bind(this),
PerformanceEvents.StandardInteractionClientInitializeAuthorizationCodeRequest,
this.logger,
this.performanceClient,
correlationId
)(silentRequest);
// Create authorize request url
const navigateUrl = await invokeAsync(
authClient.getAuthCodeUrl.bind(authClient),
PerformanceEvents.GetAuthCodeUrl,
this.logger,
this.performanceClient,
correlationId
)({
...silentRequest,
platformBroker: NativeMessageHandler.isPlatformBrokerAvailable(
this.config,
this.logger,
this.nativeMessageHandler,
silentRequest.authenticationScheme
),
});
// Create silent handler
const interactionHandler = new InteractionHandler(
authClient,
this.browserStorage,
authCodeRequest,
this.logger,
this.performanceClient
);
// Get the frame handle for the silent request
const msalFrame = await invokeAsync(
initiateAuthRequest,
PerformanceEvents.SilentHandlerInitiateAuthRequest,
this.logger,
this.performanceClient,
correlationId
)(
navigateUrl,
this.performanceClient,
this.logger,
correlationId,
this.config.system.navigateFrameWait
);
const responseType = this.config.auth.OIDCOptions.serverResponseType;
// Monitor the window for the hash. Return the string value and close the popup when the hash is received. Default timeout is 60 seconds.
const responseString = await invokeAsync(
monitorIframeForHash,
PerformanceEvents.SilentHandlerMonitorIframeForHash,
this.logger,
this.performanceClient,
correlationId
)(
msalFrame,
this.config.system.iframeHashTimeout,
this.config.system.pollIntervalMilliseconds,
this.performanceClient,
this.logger,
correlationId,
responseType
);
const serverParams = invoke(
ResponseHandler.deserializeResponse,
PerformanceEvents.DeserializeResponse,
this.logger,
this.performanceClient,
this.correlationId
)(responseString, responseType, this.logger);
if (serverParams.accountId) {
this.logger.verbose(
"Account id found in hash, calling WAM for token"
);
if (!this.nativeMessageHandler) {
throw createBrowserAuthError(
BrowserAuthErrorCodes.nativeConnectionNotEstablished
);
}
const nativeInteractionClient = new NativeInteractionClient(
this.config,
this.browserStorage,
this.browserCrypto,
this.logger,
this.eventHandler,
this.navigationClient,
this.apiId,
this.performanceClient,
this.nativeMessageHandler,
serverParams.accountId,
this.browserStorage,
correlationId
);
const { userRequestState } = ProtocolUtils.parseRequestState(
this.browserCrypto,
silentRequest.state
);
return invokeAsync(
nativeInteractionClient.acquireToken.bind(
nativeInteractionClient
),
PerformanceEvents.NativeInteractionClientAcquireToken,
this.logger,
this.performanceClient,
correlationId
)({
...silentRequest,
state: userRequestState,
prompt: silentRequest.prompt || PromptValue.NONE,
});
}
// Handle response from hash string
return invokeAsync(
interactionHandler.handleCodeResponse.bind(interactionHandler),
PerformanceEvents.HandleCodeResponse,
this.logger,
this.performanceClient,
correlationId
)(serverParams, silentRequest);
}
}