Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions change/@azure-msal-browser-edge-api-dom-support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add DOM API configuration for platform brokering, <a href=\"https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8171\">#8171</a>",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@azure-msal-common-edge-api-dom-support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Add DOM API configuration for platform brokering, <a href=\"https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8171\">#8171</a>",
"packageName": "@azure/msal-common",
"email": "[email protected]",
"dependentChangeType": "patch"
}
3 changes: 2 additions & 1 deletion lib/msal-browser/apiReview/msal-browser.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ export type BrowserSystemOptions = SystemOptions & {
navigatePopups?: boolean;
allowRedirectInIframe?: boolean;
allowPlatformBroker?: boolean;
allowPlatformBrokerWithDOM?: boolean;
nativeBrokerHandshakeTimeout?: number;
protocolMode?: ProtocolMode;
};
Expand Down Expand Up @@ -897,7 +898,7 @@ function isInPopup(): boolean;
// Warning: (ae-missing-release-tag) "isPlatformBrokerAvailable" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export function isPlatformBrokerAvailable(loggerOptions?: LoggerOptions, perfClient?: IPerformanceClient, correlationId?: string): Promise<boolean>;
export function isPlatformBrokerAvailable(domConfig: boolean, loggerOptions?: LoggerOptions, perfClient?: IPerformanceClient, correlationId?: string): Promise<boolean>;

// Warning: (ae-missing-release-tag) "IWindowStorage" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
Expand Down
41 changes: 33 additions & 8 deletions lib/msal-browser/docs/device-bound-tokens.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Acquiring Device Bound Tokens using Web Account Manager (WAM) on Windows
# Acquiring Device Bound Tokens using platform brokers

MSAL.js supports acquiring tokens from the Web Account Manager (WAM) on Windows. These tokens are bound to the device they were acquired on and are not cached in the browser's localStorage or sessionStorage.
MSAL.js supports acquiring tokens from the platform broker, say Web Account Manager (WAM) on Windows and Mac Broker on Mac. These tokens are bound to the device they were acquired on and are not cached in the browser's localStorage or sessionStorage.

## Supported Environment

This feature is currently only supported in the following environment:

- A machine running a Windows build that supports the feature (more to come on this)
- A machine running a Windows build that supports the feature (more to come on this) or a Company Portal managed Mac with a specific Mac OS version (more to come on this).
- Chrome and Edge browsers or Teams
- [Windows Accounts extension](https://chrome.google.com/webstore/detail/windows-accounts/ppnbnpeolgkicgegkbkbjmhlideopiji) (version 1.0.5 or higher) is installed if using Chrome or Edge
- App must be hosted on `https`
Expand Down Expand Up @@ -47,10 +47,35 @@ No other changes are needed to support this new feature. Any user accessing your

A working sample can be found [here](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-browser-samples/VanillaJSTestApp2.0/app/wamBroker)

## Differences when using WAM to acquire tokens
## Differences when using Platform Broker to acquire tokens

There are a few things that may behave a little differently when acquiring tokens through WAM.
There are a few things that may behave a little differently when acquiring tokens through the platform broker.

- All cache related configuration applies only to MSAL's local cache. The native broker controls its own, more secure, cache which is used instead of browser storage and it does not support configuration of its cache behavior. This means you may receive a cached token regardless of the value of request parameters such as: `forceRefresh`, `cacheLookupPolicy` or `storeInCache`. In addition, tokens received from the native broker are _not_ stored in local or session storage regardless of what you have configured on PublicClientApplication.
- If WAM needs to prompt the user for interaction a system prompt will be opened. This prompt looks a bit different from the browser popup windows you may be used to.
- Switching your account in the WAM prompt is not supported and MSAL.js will throw an error (Error Code: user_switch) if this happens. It is your app's responsibility to catch this error and handle it in a way that makes sense for your scenarios (e.g. Show an error page, retry with the new account, retry with the original account, etc.)
- All cache related configuration applies only to MSAL's local cache. The platform broker controls its own, more secure, cache which is used instead of browser storage and it does not support configuration of its cache behavior. This means you may receive a cached token regardless of the value of request parameters such as: `forceRefresh`, `cacheLookupPolicy` or `storeInCache`. In addition, tokens received from the platform broker are _not_ stored in local or session storage regardless of what you have configured on PublicClientApplication.
- If the platform broker needs to prompt the user for interaction a system prompt will be opened. This prompt looks a bit different from the browser popup windows you may be used to.
- Switching your account in the platform broker prompt is not supported and MSAL.js will throw an error (Error Code: user_switch) if this happens. It is your app's responsibility to catch this error and handle it in a way that makes sense for your scenarios (e.g. Show an error page, retry with the new account, retry with the original account, etc.)

# Acquiring Device Bound Tokens using DOM API

MSAL.js also supports acquiring tokens from the platform broker using DOM APIs in Edge. Instead of using a browser extension to communicate with the platform broker, MSAL.js can directly call a DOM API in the Edge browser, which in turn manages to invoke the platform broker to acquire tokens.

- This feature is currently only supported in the Edge browser and all other OS requirements mentioned above still apply. (more details to come).
- This feature is currently only in private-preview and requires special enablement. Please reach out to your Microsoft representative for more details.

To enable this feature, set the `allowPlatformBrokerWithDOM` flag to true in your configuration object like so:

```javascript
const msalConfig = {
auth: {
clientId: "insert-clientId",
},
system: {
allowPlatformBroker: true,
allowPlatformBrokerWithDOM: true,
},
};
```

Note: The `allowPlatformBroker` flag must also be set to true in order to use this feature. There will be a configuration error - `invalid_platform_broker_configuration` if `allowPlatformBrokerWithDOM` is set to true while `allowPlatformBroker` is false.

Eventually, in a future major version, this flag will be merged with `allowPlatformBroker` and MSAL.js will make the decision to use either the browser extension or DOM APIs based on the environment automatically.
64 changes: 34 additions & 30 deletions lib/msal-browser/src/broker/nativeBroker/PlatformAuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
Logger,
Constants,
StubPerformanceClient,
createClientConfigurationError,
ClientConfigurationErrorCodes,
} from "@azure/msal-common/browser";
import { name, version } from "../../packageMetadata.js";
import {
Expand All @@ -18,50 +20,57 @@ import {
import { PlatformAuthExtensionHandler } from "./PlatformAuthExtensionHandler.js";
import { IPlatformAuthHandler } from "./IPlatformAuthHandler.js";
import { PlatformAuthDOMHandler } from "./PlatformAuthDOMHandler.js";
import { BrowserCacheLocation } from "../../utils/BrowserConstants.js";
import { PLATFORM_AUTH_DOM_SUPPORT } from "../../cache/CacheKeys.js";
import { createNewGuid } from "../../crypto/BrowserCrypto.js";

/**
* Checks if the platform broker is available in the current environment.
* @param loggerOptions
* @param perfClient
* @param correlationId
* @returns
* @param domConfig - Whether to enable platform broker DOM API support (required)
* @param loggerOptions - Optional logger options
* @param perfClient - Optional performance client
* @param correlationId - Optional correlation ID
* @returns Promise<boolean> indicating if platform broker is available
*/
export async function isPlatformBrokerAvailable(
domConfig: boolean,
loggerOptions?: LoggerOptions,
perfClient?: IPerformanceClient,
correlationId?: string
): Promise<boolean> {
const logger = new Logger(loggerOptions || {}, name, version);
const cid = correlationId || "";

logger.trace("isPlatformBrokerAvailable called", cid);

const performanceClient = perfClient || new StubPerformanceClient();

if (typeof window === "undefined") {
logger.trace("Non-browser environment detected, returning false", cid);
logger.trace(
"Non-browser environment detected, returning false",
correlationId || createNewGuid()
);
return false;
}

return !!(await getPlatformAuthProvider(logger, performanceClient, cid));
return !!(await getPlatformAuthProvider(
domConfig
logger,
performanceClient,
correlationId || createNewGuid(),
undefined,
));
}

export async function getPlatformAuthProvider(
logger: Logger,
performanceClient: IPerformanceClient,
correlationId: string,
nativeBrokerHandshakeTimeout?: number
nativeBrokerHandshakeTimeout?: number,
enablePlatformBrokerDOMSupport?: boolean
): Promise<IPlatformAuthHandler | undefined> {
logger.trace("getPlatformAuthProvider called", correlationId);

const enablePlatformBrokerDOMSupport = isDomEnabledForPlatformAuth();

logger.trace(
`Has client allowed platform auth via DOM API: '${enablePlatformBrokerDOMSupport}'`,
correlationId
);

let platformAuthProvider: IPlatformAuthHandler | undefined;
try {
if (enablePlatformBrokerDOMSupport) {
Expand Down Expand Up @@ -96,22 +105,6 @@ export async function getPlatformAuthProvider(
return platformAuthProvider;
}

/**
* Returns true if the DOM API support for platform auth is enabled in session storage
* @returns boolean
* @deprecated
*/
export function isDomEnabledForPlatformAuth(): boolean {
let sessionStorage: Storage | undefined;
try {
sessionStorage = window[BrowserCacheLocation.SessionStorage];
// Mute errors if it's a non-browser environment or cookies are blocked.
return sessionStorage?.getItem(PLATFORM_AUTH_DOM_SUPPORT) === "true";
} catch (e) {
return false;
}
}

/**
* Returns boolean indicating whether or not the request should attempt to use native broker
* @param logger
Expand All @@ -128,6 +121,17 @@ export function isPlatformAuthAllowed(
authenticationScheme?: Constants.AuthenticationScheme
): boolean {
logger.trace("isPlatformAuthAllowed called", correlationId);

// throw an error if allowPlatformBroker is not enabled and allowPlatformBrokerWithDOM is enabled
if (
!config.system.allowPlatformBroker &&
config.system.allowPlatformBrokerWithDOM
) {
throw createClientConfigurationError(
ClientConfigurationErrorCodes.invalidPlatformBrokerConfiguration
);
}

if (!config.system.allowPlatformBroker) {
logger.trace(
"isPlatformAuthAllowed: allowPlatformBroker is not enabled, returning false",
Expand Down
5 changes: 5 additions & 0 deletions lib/msal-browser/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ export type BrowserSystemOptions = SystemOptions & {
* Flag to enable native broker support (e.g. acquiring tokens from WAM on Windows, MacBroker on Mac)
*/
allowPlatformBroker?: boolean;
/**
* Flag to enable native broker support through DOM APIs in Edge
*/
allowPlatformBrokerWithDOM?: boolean;
/**
* Sets the timeout for waiting for the native broker handshake to resolve
*/
Expand Down Expand Up @@ -280,6 +284,7 @@ export function buildConfiguration(
allowRedirectInIframe: false,
navigatePopups: true,
allowPlatformBroker: false,
allowPlatformBrokerWithDOM: false,
nativeBrokerHandshakeTimeout:
userInputSystem?.nativeBrokerHandshakeTimeout ||
DEFAULT_NATIVE_BROKER_HANDSHAKE_TIMEOUT_MS,
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-browser/src/controllers/StandardController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ export class StandardController implements IController {
this.logger,
this.performanceClient,
initCorrelationId,
this.config.system.nativeBrokerHandshakeTimeout
this.config.system.nativeBrokerHandshakeTimeout,
this.config.system.allowPlatformBrokerWithDOM
);
} catch (e) {
this.logger.verbose(e as string, initCorrelationId);
Expand Down
14 changes: 2 additions & 12 deletions lib/msal-browser/test/app/PublicClientApplication.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,13 +498,6 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
},
};

jest.spyOn(
PlatformAuthProvider,
"isDomEnabledForPlatformAuth"
).mockImplementation(() => {
return false;
});

const getPlatformAuthProviderSpy = jest.spyOn(
PlatformAuthProvider,
"getPlatformAuthProvider"
Expand Down Expand Up @@ -557,7 +550,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
expect(pca.platformAuthProvider).toBeUndefined();
});

it("creates platform auth dom handler if allowPlatformBroker is true and dom APIs are present", async () => {
it("creates platform auth dom handler if allowPlatformBrokerWithDOM is true and dom APIs are present", async () => {
const getPlatformAuthProviderSpy = jest.spyOn(
PlatformAuthProvider,
"getPlatformAuthProvider"
Expand All @@ -569,14 +562,11 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
},
system: {
allowPlatformBroker: true,
allowPlatformBrokerWithDOM: true,
},
};

pca = new PublicClientApplication(config);
window.sessionStorage.setItem(
"msal.browser.platform.auth.dom",
"true"
);

const createDOMProviderSpy = stubDOMProvider(config);
await pca.initialize();
Expand Down
Loading
Loading