Skip to content

Commit

Permalink
Merge pull request #1208 from AzureAD/authorization-code-flow-silentH…
Browse files Browse the repository at this point in the history
…andler

Authorization Code Flow for Single Page Applications: Silent Flow and Logout
  • Loading branch information
Prithvi Kanherkar committed Jan 18, 2020
2 parents fcc4ab9 + 6e01785 commit 971ff23
Show file tree
Hide file tree
Showing 28 changed files with 598 additions and 251 deletions.
53 changes: 10 additions & 43 deletions lib/msal-browser/src/app/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

// import { Logger } from "./Logger";
import { AuthOptions, INetworkModule, ILoggerCallback, LogLevel } from "msal-common";
import { AuthOptions, SystemOptions, LoggerOptions, INetworkModule, LogLevel } from "msal-common";
import { BrowserUtils } from "../utils/BrowserUtils";
import { BrowserConstants } from "../utils/BrowserConstants";
// import { TelemetryEmitter } from "./telemetry/TelemetryTypes";
Expand All @@ -13,8 +13,6 @@ import { BrowserConstants } from "../utils/BrowserConstants";
* Defaults for the Configuration Options
*/
const FRAME_TIMEOUT = 6000;
const OFFSET = 300;
const NAVIGATE_FRAME_WAIT = 500;

export type BrowserAuthOptions = AuthOptions & {
navigateToLoginRequestUrl?: boolean;
Expand All @@ -31,18 +29,6 @@ export type CacheOptions = {
storeAuthStateInCookie?: boolean;
};

/**
* Telemetry Config Options
* - applicationName - Name of the consuming apps application
* - applicationVersion - Verison of the consuming application
* - telemetryEmitter - Function where telemetry events are flushed to
*/
export type TelemetryOptions = {
applicationName: string;
applicationVersion: string;
// TODO, add onlyAddFailureTelemetry option
};

/**
* Library Specific Options
*
Expand All @@ -51,44 +37,28 @@ export type TelemetryOptions = {
* - tokenRenewalOffsetSeconds - sets the window of offset needed to renew the token before expiry
* - navigateFrameWait - sets the wait time for hidden iFrame navigation
*/
export type SystemOptions = {
loggerOptions?: BrowserLoggerOptions;
export type BrowserSystemOptions = SystemOptions & {
loggerOptions?: LoggerOptions;
networkClient?: INetworkModule;
loadFrameTimeout?: number;
tokenRenewalOffsetSeconds?: number;
navigateFrameWait?: number;
telemetry?: TelemetryOptions
};

/**
* Logger options
*
* - piiLoggingEnabled - Used to configure whether piiLogging is enabled. Defaults to false.
* - loggerCallback - Callback for logger that determines how message is broadcast. Defaults to console.
*/
export type BrowserLoggerOptions = {
piiLoggingEnabled?: boolean,
loggerCallback?: ILoggerCallback
windowHashTimeout?: number;
};

/**
* Use the configuration object to configure MSAL and initialize the UserAgentApplication.
*
* This object allows you to configure important elements of MSAL functionality:
* - auth: this is where you configure auth elements like clientID, authority used for authenticating against the Microsoft Identity Platform
* - auth: this is where you configure auth elements like clientID, authority used for authenticating against the Microsoft Identity Platform
* - cache: this is where you configure cache location and whether to store cache in cookies
* - system: this is where you can configure the logger, frame timeout etc.
* - framework: this is where you can configure the running mode of angular. More to come here soon.
* - system: this is where you can configure the network client, logger, token renewal offset, and telemetry
*/
export type Configuration = {
auth?: BrowserAuthOptions,
cache?: CacheOptions,
system?: SystemOptions
system?: BrowserSystemOptions
};

const DEFAULT_AUTH_OPTIONS: BrowserAuthOptions = {
clientId: "",
clientSecret: "",
authority: null,
validateAuthority: true,
redirectUri: () => BrowserUtils.getDefaultRedirectUri(),
Expand All @@ -101,7 +71,7 @@ const DEFAULT_CACHE_OPTIONS: CacheOptions = {
storeAuthStateInCookie: false
};

const DEFAULT_LOGGER_OPTIONS: BrowserLoggerOptions = {
const DEFAULT_LOGGER_OPTIONS: LoggerOptions = {
loggerCallback: (level: LogLevel, message: string, containsPii: boolean): void => {
if (containsPii) {
return;
Expand All @@ -124,13 +94,10 @@ const DEFAULT_LOGGER_OPTIONS: BrowserLoggerOptions = {
piiLoggingEnabled: false
};

const DEFAULT_SYSTEM_OPTIONS: SystemOptions = {
const DEFAULT_SYSTEM_OPTIONS: BrowserSystemOptions = {
loggerOptions: DEFAULT_LOGGER_OPTIONS,
networkClient: BrowserUtils.getBrowserNetworkClient(),
loadFrameTimeout: FRAME_TIMEOUT,
tokenRenewalOffsetSeconds: OFFSET,
navigateFrameWait: NAVIGATE_FRAME_WAIT,
telemetry: null
windowHashTimeout: FRAME_TIMEOUT
};

/**
Expand Down
70 changes: 55 additions & 15 deletions lib/msal-browser/src/app/PublicClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { AuthError, AuthResponse, AuthorizationCodeModule, AuthenticationParameters, INetworkModule, TokenResponse, UrlString, TemporaryCacheKeys } from "msal-common";
import { AuthError, Account, AuthResponse, AuthorizationCodeModule, AuthenticationParameters, INetworkModule, TokenResponse, UrlString, TemporaryCacheKeys, TokenRenewParameters } from "msal-common";
import { BrowserStorage } from "../cache/BrowserStorage";
import { Configuration, buildConfiguration } from "./Configuration";
import { CryptoOps } from "../crypto/CryptoOps";
Expand All @@ -12,6 +11,7 @@ import { PopupHandler } from "../interaction_handler/PopupHandler";
import { BrowserConfigurationAuthError } from "../error/BrowserConfigurationAuthError";
import { BrowserConstants } from "../utils/BrowserConstants";
import { BrowserAuthError } from "../error/BrowserAuthError";
import { BrowserUtils } from "../utils/BrowserUtils";

/**
* A type alias for an authResponseCallback function.
Expand Down Expand Up @@ -86,8 +86,12 @@ export class PublicClientApplication {
// Create auth module
this.authModule = new AuthorizationCodeModule({
auth: this.config.auth,
systemOptions: {
tokenRenewalOffsetSeconds: this.config.system.tokenRenewalOffsetSeconds,
telemetry: this.config.system.telemetry
},
loggerOptions: {
loggerCallbackInterface: this.config.system.loggerOptions.loggerCallback,
loggerCallback: this.config.system.loggerOptions.loggerCallback,
piiLoggingEnabled: this.config.system.loggerOptions.piiLoggingEnabled
},
cryptoInterface: this.browserCrypto,
Expand All @@ -114,7 +118,7 @@ export class PublicClientApplication {
const { location: { hash } } = window;
const cachedHash = this.browserStorage.getItem(TemporaryCacheKeys.URL_HASH);
try {
const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.authCallback, this.config.auth.navigateToLoginRequestUrl);
const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.config.auth.navigateToLoginRequestUrl);
const responseHash = UrlString.hashContainsKnownProperties(hash) ? hash : cachedHash;
if (responseHash) {
this.authCallback(null, await interactionHandler.handleCodeResponse(responseHash));
Expand All @@ -129,11 +133,17 @@ export class PublicClientApplication {
* any code that follows this function will not execute.
* @param {@link (AuthenticationParameters:type)}
*/
loginRedirect(request: AuthenticationParameters): void {
loginRedirect(request?: AuthenticationParameters): void {
if (!this.authCallback) {
throw BrowserConfigurationAuthError.createRedirectCallbacksNotSetError();
}

if (this.interactionInProgress()) {
throw BrowserAuthError.createInteractionInProgressError();
this.authCallback(BrowserAuthError.createInteractionInProgressError());
return;
}
const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.authCallback, this.config.auth.navigateToLoginRequestUrl);

const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.config.auth.navigateToLoginRequestUrl);
this.authModule.createLoginUrl(request).then((navigateUrl) => {
interactionHandler.showUI(navigateUrl);
});
Expand All @@ -146,11 +156,17 @@ export class PublicClientApplication {
*
* To acquire only idToken, please pass clientId as the only scope in the Authentication Parameters
*/
acquireTokenRedirect(request: AuthenticationParameters): void {
acquireTokenRedirect(request?: AuthenticationParameters): void {
if (!this.authCallback) {
throw BrowserConfigurationAuthError.createRedirectCallbacksNotSetError();
}

if (this.interactionInProgress()) {
throw BrowserAuthError.createInteractionInProgressError();
this.authCallback(BrowserAuthError.createInteractionInProgressError());
return;
}
const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.authCallback, this.config.auth.navigateToLoginRequestUrl);

const interactionHandler = new RedirectHandler(this.authModule, this.browserStorage, this.config.auth.navigateToLoginRequestUrl);
this.authModule.createAcquireTokenUrl(request).then((navigateUrl) => {
interactionHandler.showUI(navigateUrl);
});
Expand All @@ -174,7 +190,7 @@ export class PublicClientApplication {
const interactionHandler = new PopupHandler(this.authModule, this.browserStorage);
const navigateUrl = await this.authModule.createLoginUrl(request);
const popupWindow = interactionHandler.showUI(navigateUrl);
const hash = await interactionHandler.monitorWindowForHash(popupWindow, this.config.system.loadFrameTimeout, navigateUrl);
const hash = await interactionHandler.monitorWindowForHash(popupWindow, this.config.system.windowHashTimeout, navigateUrl);
return interactionHandler.handleCodeResponse(hash);
}

Expand All @@ -192,7 +208,7 @@ export class PublicClientApplication {
const interactionHandler = new PopupHandler(this.authModule, this.browserStorage);
const navigateUrl = await this.authModule.createAcquireTokenUrl(request);
const popupWindow = interactionHandler.showUI(navigateUrl);
const hash = await interactionHandler.monitorWindowForHash(popupWindow, this.config.system.loadFrameTimeout, navigateUrl);
const hash = await interactionHandler.monitorWindowForHash(popupWindow, this.config.system.windowHashTimeout, navigateUrl);
return interactionHandler.handleCodeResponse(hash);
}

Expand All @@ -202,16 +218,30 @@ export class PublicClientApplication {
* Use this function to obtain a token before every call to the API / resource provider
*
* MSAL return's a cached token when available
* Or it send's a request to the STS to obtain a new token using a hidden iframe.
* Or it send's a request to the STS to obtain a new token using a refresh token.
*
* @param {@link AuthenticationParameters}
*
* To renew idToken, please pass clientId as the only scope in the Authentication Parameters
* @returns {Promise.<TokenResponse>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
*
*/
acquireTokenSilent(request: AuthenticationParameters): Promise<TokenResponse> {
throw new Error("Method not implemented.");
async acquireTokenSilent(tokenRequest: TokenRenewParameters): Promise<TokenResponse> {
return this.authModule.renewToken(tokenRequest);
}

// #endregion

// #region Logout

/**
* Use to log out the current user, and redirect the user to the postLogoutRedirectUri.
* Default behaviour is to redirect the user to `window.location.href`.
*/
logout(): void {
this.authModule.logout().then(logoutUri => {
BrowserUtils.navigateWindow(logoutUri);
});
}

// #endregion
Expand Down Expand Up @@ -239,6 +269,16 @@ export class PublicClientApplication {
return this.authModule.getPostLogoutRedirectUri();
}

/**
* Returns the signed in account
* (the account object is created at the time of successful login)
* or null when no state is found
* @returns {@link Account} - the account object stored in MSAL
*/
public getAccount(): Account {
return this.authModule.getAccount();
}

// #endregion

// #region Helpers
Expand Down
5 changes: 2 additions & 3 deletions lib/msal-browser/src/interaction_handler/PopupHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*/
import { InteractionHandler } from "./InteractionHandler";
import { BrowserAuthError } from "../error/BrowserAuthError";
import { TemporaryCacheKeys, UrlString, StringUtils, Constants, AuthorizationCodeModule, TokenResponse } from "msal-common";
import { UrlString, StringUtils, Constants, TokenResponse } from "msal-common";
import { BrowserConstants } from "../utils/BrowserConstants";
import { BrowserStorage } from "../cache/BrowserStorage";

export class PopupHandler extends InteractionHandler {

Expand All @@ -33,7 +32,7 @@ export class PopupHandler extends InteractionHandler {
this.browserStorage.removeItem(BrowserConstants.INTERACTION_STATUS_KEY);
const codeResponse = this.authModule.handleFragmentResponse(locationHash);
this.currentWindow.close();
return this.authModule.acquireToken(null, codeResponse);
return this.authModule.acquireToken(codeResponse);
}

/**
Expand Down
9 changes: 2 additions & 7 deletions lib/msal-browser/src/interaction_handler/RedirectHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@ import { BrowserUtils } from "../utils/BrowserUtils";

export class RedirectHandler extends InteractionHandler {

private authCallback: AuthCallback;
private navigateToLoginRequestUrl: boolean;

constructor(authCodeModule: AuthorizationCodeModule, storageImpl: BrowserStorage, redirectCallback: AuthCallback, navigateToLoginRequestUrl: boolean) {
constructor(authCodeModule: AuthorizationCodeModule, storageImpl: BrowserStorage, navigateToLoginRequestUrl: boolean) {
super(authCodeModule, storageImpl);
if (!redirectCallback) {
throw BrowserConfigurationAuthError.createRedirectCallbacksNotSetError();
}
this.authCallback = redirectCallback;
this.navigateToLoginRequestUrl = navigateToLoginRequestUrl;
}

Expand Down Expand Up @@ -72,6 +67,6 @@ export class RedirectHandler extends InteractionHandler {

this.browserStorage.removeItem(BrowserConstants.INTERACTION_STATUS_KEY);
const codeResponse = this.authModule.handleFragmentResponse(locationHash);
return this.authModule.acquireToken(null, codeResponse);
return this.authModule.acquireToken(codeResponse);
}
}
41 changes: 38 additions & 3 deletions lib/msal-common/src/app/config/ModuleConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { ICrypto, PkceCodes } from "../../crypto/ICrypto";
import { AuthError } from "../../error/AuthError";
import { ILoggerCallback, LogLevel } from "../../logger/Logger";

/**
* Defaults for the Module Configuration Options
*/
const OFFSET = 300;

/**
* Use the configuration object to configure MSAL Modules and initialize the base interfaces for MSAL.
*
Expand All @@ -18,22 +23,51 @@ import { ILoggerCallback, LogLevel } from "../../logger/Logger";
* - crypto: implementation of crypto functions
*/
export type ModuleConfiguration = {
systemOptions?: SystemOptions,
loggerOptions?: LoggerOptions,
storageInterface?: ICacheStorage,
networkInterface?: INetworkModule,
cryptoInterface?: ICrypto
};

/**
* Telemetry Config Options
* - applicationName - Name of the consuming apps application
* - applicationVersion - Verison of the consuming application
* - telemetryEmitter - Function where telemetry events are flushed to
*/
export type TelemetryOptions = {
applicationName: string;
applicationVersion: string;
// TODO, add onlyAddFailureTelemetry option
};

/**
* Library Specific Options
*
* - tokenRenewalOffsetSeconds - sets the window of offset needed to renew the token before expiry
* - telemetry - Telemetry options for library network requests
*/
export type SystemOptions = {
tokenRenewalOffsetSeconds?: number;
telemetry?: TelemetryOptions
};

/**
* Logger options to configure the logging that MSAL does.
*/
export type LoggerOptions = {
loggerCallbackInterface?: ILoggerCallback,
loggerCallback?: ILoggerCallback,
piiLoggingEnabled?: boolean
};

const DEFAULT_SYSTEM_OPTIONS: SystemOptions = {
tokenRenewalOffsetSeconds: OFFSET,
telemetry: null
};

const DEFAULT_LOGGER_IMPLEMENTATION: LoggerOptions = {
loggerCallbackInterface: () => {
loggerCallback: () => {
const notImplErr = "Logger - loggerCallbackInterface() has not been implemented.";
throw AuthError.createUnexpectedError(notImplErr);
},
Expand Down Expand Up @@ -106,8 +140,9 @@ const DEFAULT_CRYPTO_IMPLEMENTATION: ICrypto = {
*
* @returns MsalConfiguration object
*/
export function buildModuleConfiguration({ loggerOptions: userLoggerOption, storageInterface: storageImplementation, networkInterface: networkImplementation, cryptoInterface: cryptoImplementation }: ModuleConfiguration): ModuleConfiguration {
export function buildModuleConfiguration({ systemOptions: userSystemOptions, loggerOptions: userLoggerOption, storageInterface: storageImplementation, networkInterface: networkImplementation, cryptoInterface: cryptoImplementation }: ModuleConfiguration): ModuleConfiguration {
const overlayedConfig: ModuleConfiguration = {
systemOptions: userSystemOptions || DEFAULT_SYSTEM_OPTIONS,
loggerOptions: userLoggerOption || DEFAULT_LOGGER_IMPLEMENTATION,
storageInterface: storageImplementation || DEFAULT_STORAGE_IMPLEMENTATION,
networkInterface: networkImplementation || DEFAULT_NETWORK_IMPLEMENTATION,
Expand Down

0 comments on commit 971ff23

Please sign in to comment.