Skip to content

Commit

Permalink
Support CreatePCA pattern for NAA apps (#7112)
Browse files Browse the repository at this point in the history
This PR adds two new public APIs:

* `createNestablePublicClientApplication` and
`createStandardPublicClientApplication` as exportable functions.
* Moving these functions out of PCA class helps us to eventually clean
up and provide a better size output for NAA only apps.

The usage pattern still remains the same:
- Create nestedPCA if the OperatingContext matches
- Fall back to standardPCA as default

`PublicClientNext` will eventually see removal in any NAA support as we
make changes. We will add notice that `PCANext` will not be following
semver in the upcoming releases, so we can experiment with the future
changes using that class once this pattern is adapted.

---------

Co-authored-by: Thomas Norling <[email protected]>
  • Loading branch information
sameerag and tnorling authored May 21, 2024
1 parent 89ff611 commit adaa895
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add createNestablePublicClientApplication()",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
36 changes: 24 additions & 12 deletions lib/msal-browser/apiReview/msal-browser.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,18 @@ export type Configuration = {
// @public
function createGuid(): string;

// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-missing-release-tag) "createNestablePublicClientApplication" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export function createNestablePublicClientApplication(configuration: Configuration): Promise<IPublicClientApplication>;

// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-missing-release-tag) "createStandardPublicClientApplication" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export function createStandardPublicClientApplication(configuration: Configuration): Promise<IPublicClientApplication>;

// Warning: (ae-missing-release-tag) "cryptoKeyNotFound" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
Expand Down Expand Up @@ -1656,18 +1668,18 @@ export type WrapperSKU = (typeof WrapperSKU)[keyof typeof WrapperSKU];

// Warnings were encountered during analysis:
//
// src/app/PublicClientNext.ts:63:8 - (tsdoc-undefined-tag) The TSDoc tag "@constructor" is not defined in this configuration
// src/app/PublicClientNext.ts:72:87 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:72:60 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:78:64 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:78:77 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:78:90 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:78:55 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:78:70 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:78:79 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:81:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/app/PublicClientNext.ts:82:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/config/Configuration.ts:231:5 - (ae-forgotten-export) The symbol "InternalAuthOptions" needs to be exported by the entry point index.d.ts
// src/app/PublicClientNext.ts:66:8 - (tsdoc-undefined-tag) The TSDoc tag "@constructor" is not defined in this configuration
// src/app/PublicClientNext.ts:75:87 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:75:60 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:81:64 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:81:77 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:81:90 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
// src/app/PublicClientNext.ts:81:55 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:81:70 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:81:79 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
// src/app/PublicClientNext.ts:84:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/app/PublicClientNext.ts:85:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// src/config/Configuration.ts:233:5 - (ae-forgotten-export) The symbol "InternalAuthOptions" needs to be exported by the entry point index.d.ts
// src/index.ts:8:12 - (tsdoc-characters-after-block-tag) The token "@azure" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@"
// src/index.ts:8:4 - (tsdoc-undefined-tag) The TSDoc tag "@module" is not defined in this configuration
// src/navigation/NavigationClient.ts:36:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
Expand Down
145 changes: 99 additions & 46 deletions lib/msal-browser/docs/initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,59 @@
Before you get started, please ensure you have completed all the [prerequisites](../README.md#prerequisites).

In this document:
- [Initialization of MSAL](#initialization-of-msal)
- [Initializing the PublicClientApplication object](#initializing-the-publicclientapplication-object)
- [(Optional) Configure Authority](#optional-configure-authority)
- [(Optional) Configure Redirect URI](#optional-configure-redirect-uri)
- [(Optional) Additional Configuration](#optional-additional-configuration)
- [Choosing an Interaction Type](#choosing-an-interaction-type)
- [Popup APIs](#popup-apis)
- [Redirect APIs](#redirect-apis)
- [Next Steps](#next-steps)

- [Initialization of MSAL](#initialization-of-msal)
- [CreatePCA pattern](#createpca-pattern)
- [Standard Configuration](#standard-configuration)
- [Nested App Configuration](#nested-app-configuration)
- [Initializing the PublicClientApplication object](#initializing-the-publicclientapplication-object)
- [(Optional) Configure Authority](#optional-configure-authority)
- [(Optional) Configure Redirect URI](#optional-configure-redirect-uri)
- [(Optional) Additional Configuration](#optional-additional-configuration)
- [Choosing an Interaction Type](#choosing-an-interaction-type)
- [Popup APIs](#popup-apis)
- [Redirect APIs](#redirect-apis)
- [Next Steps](#next-steps)

## CreatePCA pattern

We are introducing a `CreatePCA` pattern in MSAL JS, to allow for apps to choose the type of `PublicClientApplication` they prefer. Current options include `Standard` and `Nestable` configurations. In the future, we will extend this to introduce more configurations.

### Standard Configuration

If you are using MSAL.js in a single-page application, you should import msal-browser to create an [IPublicClientApplication](.\lib\msal-browser\src\app\IPublicClientApplication.ts) instance with `createStandardPublicClientApplication`. This function will create a PublicClientApplication instance with the standard configuration.

```javascript
import * as msal from "@azure/msal-browser";

const pca = msal.createStandardPublicClientApplication({
auth: {
clientId: "ENTER_CLIENT_ID",
authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
},
});
```

### Nested App Configuration

If your app is an iframed Nested app and delegating its authentication to a hub SDK (which is either a SPA or a desktop application running in MetaOS framework), you should import msal-browser to create a IPublicClientApplication instance with `createNestablePublicClientApplication`. This function will create a PublicClientApplication instance with the NAA configuration.

```javascript
import * as msal from "@azure/msal-browser";

const nestablePca = msal.createNestablePublicClientApplication({
auth: {
clientId: "ENTER_CLIENT_ID",
authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
},
});
```

Please note the below guidance before opting in for Nested app authentication:

- `supportsNestedAppAuth` in MSAL Browser configuration will be deprecated in the next major version. Please use `createNestablePublicClientApplication` instead.
- `createNestablePublicClientApplication` will fall back to `createStandardPublicClientApplication` if nested app bridge is unavailable or the Hub is not configured to support nested app authentication.
- If an application does not want to be Nested App, it should use `createStandardPublicClientApplication` instead.

## Initializing the PublicClientApplication object

Expand All @@ -26,8 +70,8 @@ import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
auth: {
clientId: 'your_client_id'
}
clientId: "your_client_id",
},
};

const msalInstance = new PublicClientApplication(msalConfig);
Expand All @@ -43,65 +87,70 @@ import { PublicClientApplication } from "@azure/msal-browser";

const msalConfig = {
auth: {
clientId: 'your_client_id'
}
clientId: "your_client_id",
},
};

const msalInstance = await PublicClientApplication.createPublicClientApplication(msalConfig);
const msalInstance =
await PublicClientApplication.createPublicClientApplication(msalConfig);
```

## (Optional) Configure Authority

By default, MSAL is configured with the `common` tenant, which is used for multi-tenant applications and applications allowing personal accounts (not B2C).

```javascript
const msalConfig = {
auth: {
clientId: 'your_client_id',
authority: 'https://login.microsoftonline.com/common/'
}
clientId: "your_client_id",
authority: "https://login.microsoftonline.com/common/",
},
};
```

If your application audience is a single tenant, you must provide an authority with your tenant id like below:

```javascript
const msalConfig = {
auth: {
clientId: 'your_client_id',
authority: 'https://login.microsoftonline.com/{your_tenant_id}'
}
clientId: "your_client_id",
authority: "https://login.microsoftonline.com/{your_tenant_id}",
},
};
```

If your application is using a separate OIDC-compliant authority like `"https://login.live.com"` or an IdentityServer, you will need to provide it in the `knownAuthorities` field and set your `protocolMode` to `"OIDC"`.

```javascript
const msalConfig = {
auth: {
clientId: 'your_client_id',
authority: 'https://login.live.com',
clientId: "your_client_id",
authority: "https://login.live.com",
knownAuthorities: ["login.live.com"],
protocolMode: "OIDC"
}
protocolMode: "OIDC",
},
};
```

**Note**: The `protocolMode` configuration option, which tells MSAL whether to enable AAD-specific quirks, changes the following behavior:

- Authority metadata (since `v2.4.0`):
- When set to `OIDC`, the library will not include `/v2.0/` in the authority path when fetching authority metadata.
- When set to `AAD` (the default value), the library will include `/v2.0/` in the authority path when fetching authority metadata.
- Authority metadata (since `v2.4.0`):
- When set to `OIDC`, the library will not include `/v2.0/` in the authority path when fetching authority metadata.
- When set to `AAD` (the default value), the library will include `/v2.0/` in the authority path when fetching authority metadata.

For more information on authority, please refer to: [Authority in MSAL](../../msal-common/docs/authority.md).

## (Optional) Configure Redirect URI

By default, MSAL is configured to set the redirect URI to the current page that it is running on. If you would like to receive the authorization code on a different page than the one running MSAL, you can set this in the configuration:

```javascript
const msalConfig = {
auth: {
clientId: 'your_client_id',
authority: 'https://login.microsoftonline.com/{your_tenant_id}',
redirectUri: 'https://contoso.com'
}
clientId: "your_client_id",
authority: "https://login.microsoftonline.com/{your_tenant_id}",
redirectUri: "https://contoso.com",
},
};
```

Expand All @@ -115,18 +164,19 @@ MSAL has additional configuration options which you can review [here](./configur

The following flow diagram can help you avoid unnecessary authentication prompts when an account (or multiple accounts) is available for SSO.

![MSAL.js boot flow diagram](images/msaljs-boot-flow.png )
![MSAL.js boot flow diagram](images/msaljs-boot-flow.png)

## Choosing an Interaction Type

In the browser, there are two ways you can present the login screen to your users from your application:
- [presenting a popup window from the current page](#popup-apis)
- [redirecting the browser window to the login server](#redirect-apis)

- [presenting a popup window from the current page](#popup-apis)
- [redirecting the browser window to the login server](#redirect-apis)

### Popup APIs

- `loginPopup`
- `acquireTokenPopup`
- `loginPopup`
- `acquireTokenPopup`

The popup APIs use ES6 Promises that resolve when the authentication flow in the popup concludes and returns to the redirect URI specified, or reject if there are issues in the code or the popup is blocked.

Expand All @@ -136,27 +186,30 @@ When using popup APIs we recommend setting the `redirectUri` to a blank page or

```javascript
msalInstance.loginPopup({
redirectUri: "http://localhost:3000/blank.html"
redirectUri: "http://localhost:3000/blank.html",
});
```

### Redirect APIs

- `loginRedirect`
- `acquireTokenRedirect`
- `loginRedirect`
- `acquireTokenRedirect`

Note: If you are using `msal-angular` or `msal-react`, redirects are handled differently, and you should see the [`msal-angular` redirect doc](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/redirects.md) and [`msal-react` FAQ](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/FAQ.md#how-do-i-handle-the-redirect-flow-in-a-react-app) for more details.

The redirect APIs are asynchronous (i.e. return a promise) `void` functions which redirect the browser window after caching some basic info. If you choose to use the redirect APIs, be aware that **you MUST call `handleRedirectPromise()` to correctly handle the API**. You can use the following function to perform an action when this token exchange is completed:

```javascript
msalInstance.handleRedirectPromise().then((tokenResponse) => {
// Check if the tokenResponse is null
// If the tokenResponse !== null, then you are coming back from a successful authentication redirect.
// If the tokenResponse === null, you are not coming back from an auth redirect.
}).catch((error) => {
// handle error, either in the library or coming back from the server
});
msalInstance
.handleRedirectPromise()
.then((tokenResponse) => {
// Check if the tokenResponse is null
// If the tokenResponse !== null, then you are coming back from a successful authentication redirect.
// If the tokenResponse === null, you are not coming back from an auth redirect.
})
.catch((error) => {
// handle error, either in the library or coming back from the server
});
```

This will also allow you to retrieve tokens on page reload. See the [onPageLoad sample](../../../samples/msal-browser-samples/VanillaJSTestApp2.0/app/onPageLoad/) for more information on usage.
Expand Down
51 changes: 43 additions & 8 deletions lib/msal-browser/src/app/PublicClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { AuthenticationResult } from "../response/AuthenticationResult";
import { EventCallbackFunction } from "../event/EventMessage";
import { ClearCacheRequest } from "../request/ClearCacheRequest";
import { EndSessionPopupRequest } from "../request/EndSessionPopupRequest";
import { NestedAppAuthController } from "../controllers/NestedAppAuthController";
import { NestedAppOperatingContext } from "../operatingcontext/NestedAppOperatingContext";

/**
* The PublicClientApplication class is the object exposed by the library to perform authentication and authorization functions in Single Page Applications
Expand All @@ -36,6 +38,7 @@ import { EndSessionPopupRequest } from "../request/EndSessionPopupRequest";
export class PublicClientApplication implements IPublicClientApplication {
protected controller: IController;

// creates StandardController and passes it to the PublicClientApplication
public static async createPublicClientApplication(
configuration: Configuration
): Promise<IPublicClientApplication> {
Expand Down Expand Up @@ -70,14 +73,9 @@ export class PublicClientApplication implements IPublicClientApplication {
* @param IController Optional parameter to explictly set the controller. (Will be removed when we remove public constructor)
*/
public constructor(configuration: Configuration, controller?: IController) {
if (controller) {
this.controller = controller;
} else {
const standardOperatingContext = new StandardOperatingContext(
configuration
);
this.controller = new StandardController(standardOperatingContext);
}
this.controller =
controller ||
new StandardController(new StandardOperatingContext(configuration));
}

/**
Expand Down Expand Up @@ -417,3 +415,40 @@ export class PublicClientApplication implements IPublicClientApplication {
return this.controller.clearCache(logoutRequest);
}
}

/**
* creates NestedAppAuthController and passes it to the PublicClientApplication,
* falls back to StandardController if NestedAppAuthController is not available
*
* @param configuration
* @returns IPublicClientApplication
*
*/
export async function createNestablePublicClientApplication(
configuration: Configuration
): Promise<IPublicClientApplication> {
const nestedAppAuth = new NestedAppOperatingContext(configuration);
await nestedAppAuth.initialize();

if (nestedAppAuth.isAvailable()) {
const controller = new NestedAppAuthController(nestedAppAuth);
return new PublicClientApplication(configuration, controller);
}

return createStandardPublicClientApplication(configuration);
}

/**
* creates PublicClientApplication using StandardController
*
* @param configuration
* @returns IPublicClientApplication
*
*/
export async function createStandardPublicClientApplication(
configuration: Configuration
): Promise<IPublicClientApplication> {
const pca = new PublicClientApplication(configuration);
await pca.initialize();
return pca;
}
3 changes: 3 additions & 0 deletions lib/msal-browser/src/app/PublicClientNext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ import { UnknownOperatingContext } from "../operatingcontext/UnknownOperatingCon
* The goals of these changes are to provide a clean separation of behavior between different operating contexts (Nested App Auth, Platform Brokers, Plain old Browser, etc.)
* while still providing a consistent API surface for developers.
*
* Please use PublicClientApplication for any prod/real-world scenarios.
* Note: PublicClientNext is experimental and subject to breaking changes without following semver
*
*/
export class PublicClientNext implements IPublicClientApplication {
/*
Expand Down
4 changes: 3 additions & 1 deletion lib/msal-browser/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ export type BrowserAuthOptions = {
*/
skipAuthorityMetadataCache?: boolean;
/**
* App supports nested app auth or not; defaults to false
* App supports nested app auth or not; defaults to
*
* @deprecated This flag is deprecated and will be removed in the next major version. createNestablePublicClientApplication should be used instead.
*/
supportsNestedAppAuth?: boolean;
};
Expand Down
Loading

0 comments on commit adaa895

Please sign in to comment.