Skip to content

Commit

Permalink
Update Nested App Auth internal schema (#6737)
Browse files Browse the repository at this point in the history
Currently Nested App Auth uses a schema that contains union types such
as response.body where the body can contain a different structure
depending on the request type. This works in JavaScript that uses a
dynamic JSON parser, but some native implementations require a strongly
typed schema. There are workarounds on native, but it is easier for all
platforms to support Nested App Auth if union types are removed from the
schema.

Also remove AccountByHomeIdRequest, AccountByLocalIdRequest, and
AccountByUsernameRequest that are not currently implemented.
  • Loading branch information
codexeon authored Dec 16, 2023
1 parent 4aacf4d commit 648501e
Show file tree
Hide file tree
Showing 17 changed files with 224 additions and 271 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Change Nested App Auth internal schema (#6737)",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "patch"
}
16 changes: 0 additions & 16 deletions lib/msal-browser/src/naa/AccountRequests.ts

This file was deleted.

12 changes: 12 additions & 0 deletions lib/msal-browser/src/naa/AuthResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { AccountInfo } from "./AccountInfo";
import { TokenResponse } from "./TokenResponse";

export type AuthResult = {
token: TokenResponse;
account: AccountInfo;
};
127 changes: 59 additions & 68 deletions lib/msal-browser/src/naa/BridgeProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@
*/

import { AccountInfo } from "./AccountInfo";
import {
AccountByHomeIdRequest,
AccountByLocalIdRequest,
AccountByUsernameRequest,
} from "./AccountRequests";
import { AuthBridge, AuthBridgeResponse } from "./AuthBridge";
import { AuthResult } from "./AuthResult";
import { BridgeCapabilities } from "./BridgeCapabilities";
import { BridgeError } from "./BridgeError";
import { BridgeRequest } from "./BridgeRequest";
import { BridgeRequestEnvelope, BridgeMethods } from "./BridgeRequestEnvelope";
import { BridgeResponseEnvelope } from "./BridgeResponseEnvelope";
import { BridgeStatusCode } from "./BridgeStatusCode";
import { IBridgeProxy } from "./IBridgeProxy";
import { InitializeBridgeResponse } from "./InitializeBridgeResponse";
import { InitContext } from "./InitContext";
import { TokenRequest } from "./TokenRequest";
import { TokenResponse } from "./TokenResponse";

declare global {
interface Window {
Expand All @@ -31,8 +28,7 @@ declare global {
* platform broker
*/
export class BridgeProxy implements IBridgeProxy {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static bridgeRequests: any[] = [];
static bridgeRequests: BridgeRequest[] = [];
static crypto: Crypto;
sdkName: string;
sdkVersion: string;
Expand All @@ -44,7 +40,7 @@ export class BridgeProxy implements IBridgeProxy {
* @remarks This method will be called by the create factory method
* @remarks If the bridge is not available, this method will throw an error
*/
protected static async initializeNestedAppAuthBridge(): Promise<InitializeBridgeResponse> {
protected static async initializeNestedAppAuthBridge(): Promise<InitContext> {
if (window === undefined) {
throw new Error("window is undefined");
}
Expand Down Expand Up @@ -75,22 +71,22 @@ export class BridgeProxy implements IBridgeProxy {
1
);
if (responseEnvelope.success) {
request.resolve(responseEnvelope.body);
request.resolve(responseEnvelope);
} else {
request.reject(responseEnvelope.body);
request.reject(responseEnvelope.error);
}
}
}
);

const promise = new Promise<InitializeBridgeResponse>(
const bridgeResponse = await new Promise<BridgeResponseEnvelope>(
(resolve, reject) => {
const message: BridgeRequestEnvelope = {
messageType: "NestedAppAuthRequest",
method: "GetInitContext",
requestId: BridgeProxy.getRandomId(),
};
const request: BridgeRequest<InitializeBridgeResponse> = {
const request: BridgeRequest = {
requestId: message.requestId,
method: message.method,
resolve: resolve,
Expand All @@ -103,7 +99,9 @@ export class BridgeProxy implements IBridgeProxy {
}
);

return await promise;
return BridgeProxy.validateBridgeResultOrThrow(
bridgeResponse.initContext
);
} catch (error) {
window.console.log(error);
throw error;
Expand All @@ -117,51 +115,37 @@ export class BridgeProxy implements IBridgeProxy {
/**
* getTokenInteractive - Attempts to get a token interactively from the bridge
* @param request A token request
* @returns a promise that resolves to a token response or rejects with a BridgeError
* @returns a promise that resolves to an auth result or rejects with a BridgeError
*/
public getTokenInteractive(request: TokenRequest): Promise<TokenResponse> {
return this.sendRequest<TokenResponse>("GetTokenPopup", request);
public getTokenInteractive(request: TokenRequest): Promise<AuthResult> {
return this.getToken("GetTokenPopup", request);
}

/**
* getTokenSilent Attempts to get a token silently from the bridge
* @param request A token request
* @returns a promise that resolves to a token response or rejects with a BridgeError
* @returns a promise that resolves to an auth result or rejects with a BridgeError
*/
public getTokenSilent(request: TokenRequest): Promise<TokenResponse> {
return this.sendRequest<TokenResponse>("GetToken", request);
public getTokenSilent(request: TokenRequest): Promise<AuthResult> {
return this.getToken("GetToken", request);
}

/**
* getAccountInfo - Gets account information from the bridge
*
* @param request A request for account information
*/
public getAccountInfo(
request:
| AccountByHomeIdRequest
| AccountByLocalIdRequest
| AccountByUsernameRequest
): Promise<AccountInfo> {
let method: BridgeMethods = "GetAccountByHomeId";

if ((request as AccountByHomeIdRequest).homeAccountId !== undefined) {
method = "GetAccountByHomeId";
}

if ((request as AccountByLocalIdRequest).localAccountId !== undefined) {
method = "GetAccountByLocalId";
}

if ((request as AccountByUsernameRequest).username !== undefined) {
method = "GetAccountByUsername";
}

return this.sendRequest<AccountInfo>(method, request);
private async getToken(
requestType: BridgeMethods,
request: TokenRequest
): Promise<AuthResult> {
const result = await this.sendRequest(requestType, {
tokenParams: request,
});
return {
token: BridgeProxy.validateBridgeResultOrThrow(result.token),
account: BridgeProxy.validateBridgeResultOrThrow(result.account),
};
}

public getActiveAccount(): Promise<AccountInfo> {
return this.sendRequest<AccountInfo>("GetActiveAccount", undefined);
public async getActiveAccount(): Promise<AccountInfo> {
const result = await this.sendRequest("GetActiveAccount");
return BridgeProxy.validateBridgeResultOrThrow(result.account);
}

public getHostCapabilities(): BridgeCapabilities | null {
Expand All @@ -173,36 +157,43 @@ export class BridgeProxy implements IBridgeProxy {
* @param request A token request
* @returns a promise that resolves to a response of provided type or rejects with a BridgeError
*/
private sendRequest<TResponse>(
private sendRequest(
method: BridgeMethods,
request:
| TokenRequest
| AccountByHomeIdRequest
| AccountByLocalIdRequest
| AccountByUsernameRequest
| undefined
): Promise<TResponse> {
requestParams?: Partial<BridgeRequestEnvelope>
): Promise<BridgeResponseEnvelope> {
const message: BridgeRequestEnvelope = {
messageType: "NestedAppAuthRequest",
method: method,
requestId: BridgeProxy.getRandomId(),
body: request,
...requestParams,
};

const promise = new Promise<TResponse>((resolve, reject) => {
const request: BridgeRequest<TResponse> = {
requestId: message.requestId,
method: message.method,
resolve: resolve,
reject: reject,
};
BridgeProxy.bridgeRequests.push(request);
window.nestedAppAuthBridge.postMessage(JSON.stringify(message));
});
const promise = new Promise<BridgeResponseEnvelope>(
(resolve, reject) => {
const request: BridgeRequest = {
requestId: message.requestId,
method: message.method,
resolve: resolve,
reject: reject,
};
BridgeProxy.bridgeRequests.push(request);
window.nestedAppAuthBridge.postMessage(JSON.stringify(message));
}
);

return promise;
}

private static validateBridgeResultOrThrow<T>(input: T | undefined): T {
if (input === undefined) {
const bridgeError: BridgeError = {
status: BridgeStatusCode.NestedAppAuthUnavailable,
};
throw bridgeError;
}
return input;
}

/**
* Private constructor for BridgeProxy
* @param sdkName The name of the SDK being used to make requests on behalf of the app
Expand Down
8 changes: 6 additions & 2 deletions lib/msal-browser/src/naa/BridgeRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
* Licensed under the MIT License.
*/

export type BridgeRequest<TResponse> = {
import { BridgeResponseEnvelope } from "./BridgeResponseEnvelope";

export type BridgeRequest = {
requestId: string;
method: string;
resolve: (value: TResponse | PromiseLike<TResponse>) => void;
resolve: (
value: BridgeResponseEnvelope | PromiseLike<BridgeResponseEnvelope>
) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (reason?: any) => void;
};
14 changes: 1 addition & 13 deletions lib/msal-browser/src/naa/BridgeRequestEnvelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,11 @@
*/

import { TokenRequest } from "./TokenRequest";
import {
AccountByHomeIdRequest,
AccountByLocalIdRequest,
AccountByUsernameRequest,
} from "./AccountRequests";

export type BridgeMethods =
| "GetToken"
| "GetActiveAccount"
| "GetAllAccounts"
| "GetAccountByHomeId"
| "GetAccountByLocalId"
| "GetAccountByUsername"
| "GetInitContext"
| "GetTokenPopup";

Expand All @@ -27,11 +19,7 @@ export type BridgeRequestEnvelope = {
clientLibrary?: string;
clientLibraryVersion?: string;
requestId: string;
body?:
| TokenRequest
| AccountByHomeIdRequest
| AccountByLocalIdRequest
| AccountByUsernameRequest;
tokenParams?: TokenRequest;
};

export function isBridgeRequestEnvelope(
Expand Down
12 changes: 5 additions & 7 deletions lib/msal-browser/src/naa/BridgeResponseEnvelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
import { BridgeError } from "./BridgeError";
import { TokenResponse } from "./TokenResponse";
import { AccountInfo } from "./AccountInfo";
import { InitializeBridgeResponse } from "./InitializeBridgeResponse";
import { InitContext } from "./InitContext";

export type BridgeResponseEnvelope = {
messageType: "NestedAppAuthResponse";
requestId: string;
success: boolean; // false if body is error
body:
| TokenResponse
| BridgeError
| AccountInfo
| AccountInfo[]
| InitializeBridgeResponse;
token?: TokenResponse;
error?: BridgeError;
account?: AccountInfo;
initContext?: InitContext;
};
22 changes: 12 additions & 10 deletions lib/msal-browser/src/naa/BridgeStatusCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
* Licensed under the MIT License.
*/

export enum BridgeStatusCode {
USER_INTERACTION_REQUIRED = "USER_INTERACTION_REQUIRED",
USER_CANCEL = "USER_CANCEL",
NO_NETWORK = "NO_NETWORK",
TRANSIENT_ERROR = "TRANSIENT_ERROR",
PERSISTENT_ERROR = "PERSISTENT_ERROR",
DISABLED = "DISABLED",
ACCOUNT_UNAVAILABLE = "ACCOUNT_UNAVAILABLE",
NESTED_APP_AUTH_UNAVAILABLE = "NESTED_APP_AUTH_UNAVAILABLE", // NAA is unavailable in the current context, can retry with standard browser based auth
}
export const BridgeStatusCode = {
UserInteractionRequired: "USER_INTERACTION_REQUIRED",
UserCancel: "USER_CANCEL",
NoNetwork: "NO_NETWORK",
TransientError: "TRANSIENT_ERROR",
PersistentError: "PERSISTENT_ERROR",
Disabled: "DISABLED",
AccountUnavailable: "ACCOUNT_UNAVAILABLE",
NestedAppAuthUnavailable: "NESTED_APP_AUTH_UNAVAILABLE", // NAA is unavailable in the current context, can retry with standard browser based auth
} as const;
export type BridgeStatusCode =
(typeof BridgeStatusCode)[keyof typeof BridgeStatusCode];
17 changes: 3 additions & 14 deletions lib/msal-browser/src/naa/IBridgeProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,13 @@
*/

import { AccountInfo } from "./AccountInfo";
import {
AccountByHomeIdRequest,
AccountByLocalIdRequest,
AccountByUsernameRequest,
} from "./AccountRequests";
import { AuthResult } from "./AuthResult";
import { BridgeCapabilities } from "./BridgeCapabilities";
import { TokenRequest } from "./TokenRequest";
import { TokenResponse } from "./TokenResponse";

export interface IBridgeProxy {
getTokenInteractive(request: TokenRequest): Promise<TokenResponse>;
getTokenSilent(request: TokenRequest): Promise<TokenResponse>;
getAccountInfo(
request:
| AccountByHomeIdRequest
| AccountByLocalIdRequest
| AccountByUsernameRequest
): Promise<AccountInfo>;
getTokenInteractive(request: TokenRequest): Promise<AuthResult>;
getTokenSilent(request: TokenRequest): Promise<AuthResult>;
getActiveAccount(): Promise<AccountInfo>;
getHostCapabilities(): BridgeCapabilities | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { BridgeCapabilities } from "./BridgeCapabilities";

export interface InitializeBridgeResponse {
export interface InitContext {
capabilities?: BridgeCapabilities;
sdkName: string;
sdkVersion: string;
Expand Down
Loading

0 comments on commit 648501e

Please sign in to comment.