Skip to content

Commit 1e56131

Browse files
committed
Switched ARM authentication for editor:
- from config params to query params - from CORS proxy to dedicated service
1 parent 16e6921 commit 1e56131

File tree

5 files changed

+100
-30
lines changed

5 files changed

+100
-30
lines changed

src/authentication/armAuthenticator.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
11
import * as Msal from "@azure/msal-browser";
2+
import { ISettingsProvider } from "@paperbits/common/configuration/ISettingsProvider";
23
import { IAuthenticator, AccessToken } from ".";
4+
import { SettingNames } from "../constants";
35

46

5-
const aadClientId = "a962e1ed-5694-4abe-9e9b-d08d35877efc"; // test app
6-
const loginRequest = { scopes: ["openid", "profile", "https://management.azure.com/user_impersonation"], account: null };
7-
const authority = "https://login.microsoftonline.com/common";
8-
const redirectUri = "https://apimanagement-cors-proxy-df.azure-api.net/portal/signin-aad";
7+
// const aadClientId = "a962e1ed-5694-4abe-9e9b-d08d35877efc"; // test app PROD
8+
// const aadClientId = "4c6edb5e-d0fb-4ca1-ac29-8c181c1a9522"; // test app PPE
9+
10+
// const authority = "https://login.microsoftonline.com/common"; // PROD
11+
// const authority = "https://login.windows-ppe.net/common"; // PPE
12+
13+
// const redirectUri = "https://apimanagement-cors-proxy-df.azure-api.net/portal/signin-aad";
14+
15+
// login example
16+
// http://localhost:8080?subscriptionId=b8ff56dc-3bc7-4174-a1e8-3726ab15d0e2&resourceGroupName=Admin-ResourceGroup&serviceName=igo-east
917

1018
export class ArmAuthenticator implements IAuthenticator {
1119
private accessToken: AccessToken;
20+
private loginRequest: Msal.SilentRequest;
1221
private msalInstance: Msal.PublicClientApplication;
1322
private authPromise: Promise<AccessToken>;
1423

15-
constructor() {
24+
private initializePromise: Promise<void>;
25+
26+
constructor(
27+
private readonly settingsProvider: ISettingsProvider
28+
) {}
29+
30+
private async ensureInitialized(): Promise<void> {
31+
if (!this.initializePromise) {
32+
this.initializePromise = this.initInstance();
33+
}
34+
return this.initializePromise;
35+
}
36+
37+
private async initInstance(): Promise<void> {
38+
const settings = await this.settingsProvider.getSettings();
39+
const aadClientId = settings[SettingNames.aadClientId];
40+
const authority = settings[SettingNames.aadAuthority];
41+
this.loginRequest = settings[SettingNames.aadLoginRequest];
42+
43+
if (!aadClientId || !authority || !this.loginRequest) {
44+
throw new Error("Settings was not provided for Msal.Configuration");
45+
}
46+
47+
const redirectUri = location.origin;
48+
1649
const msalConfig: Msal.Configuration = {
1750
auth: {
1851
clientId: aadClientId,
@@ -29,8 +62,8 @@ export class ArmAuthenticator implements IAuthenticator {
2962
}
3063

3164
public async checkCallbacks(): Promise<Msal.AuthenticationResult> {
65+
await this.ensureInitialized();
3266
try {
33-
3467
return await this.msalInstance.handleRedirectPromise();
3568
}
3669
catch (error) {
@@ -49,6 +82,7 @@ export class ArmAuthenticator implements IAuthenticator {
4982
}
5083

5184
private async tryAcquireToken(): Promise<AccessToken> {
85+
await this.ensureInitialized();
5286
const account = this.getAccount();
5387

5488
if (!account) {
@@ -59,22 +93,22 @@ export class ArmAuthenticator implements IAuthenticator {
5993
return parsedToken;
6094
}
6195

62-
await this.msalInstance.acquireTokenRedirect(loginRequest);
96+
await this.msalInstance.acquireTokenRedirect(this.loginRequest);
6397
return;
6498
}
6599

66-
loginRequest.account = account;
100+
this.loginRequest.account = account;
67101

68102
try {
69-
const result = await this.msalInstance.acquireTokenSilent(loginRequest);
103+
const result = await this.msalInstance.acquireTokenSilent(this.loginRequest);
70104
const token = AccessToken.parse(`${result.tokenType} ${result.accessToken}`);
71105

72106
return token;
73107
}
74108
catch (error) {
75109
if (error instanceof Msal.InteractionRequiredAuthError) {
76110
// fallback to interaction when silent call fails
77-
await this.msalInstance.acquireTokenRedirect(loginRequest);
111+
await this.msalInstance.acquireTokenRedirect(this.loginRequest);
78112
}
79113
else {
80114
console.warn(error);

src/components/app/app.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,18 @@ export class App {
4242

4343
@OnMounted()
4444
public async initialize(): Promise<void> {
45-
const settings = await this.settingsProvider.getSettings();
46-
47-
const subscriptionId = settings["subscriptionId"];
48-
const resourceGroupName = settings[SettingNames.resourceGroupName];
49-
const serviceName = settings[SettingNames.serviceName];
50-
const armEndpoint = settings[SettingNames.armEndpoint] || "management.azure.com";
51-
52-
if (subscriptionId && resourceGroupName && serviceName) {
53-
const managementApiUrl = `https://${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${serviceName}`;
54-
await this.settingsProvider.setSetting(SettingNames.managementApiUrl, managementApiUrl);
55-
await this.settingsProvider.setSetting(SettingNames.backendUrl, `https://${serviceName}.developer.azure-api/net`);
45+
try {
46+
await this.armService.loadSessionSettings();
47+
} catch (error) {
48+
this.viewManager.addToast(startupError, error);
49+
return;
5650
}
57-
5851
const runtimeSettings = await this.getRuntimeSettings();
5952
this.sessionManager.setItem("designTimeSettings", runtimeSettings);
6053
this.viewManager.setHost({ name: "page-host" });
6154
this.viewManager.showToolboxes();
6255

56+
const settings = await this.settingsProvider.getSettings();
6357

6458
if (!settings[SettingNames.managementApiUrl]) {
6559
this.viewManager.addToast(startupError, `Management API URL is missing. See setting <i>managementApiUrl</i> in the configuration file <i>config.design.json</i>`);
@@ -70,7 +64,7 @@ export class App {
7064
const developerPortalType = settings[SettingNames.developerPortalType] || DeveloperPortalType.selfHosted;
7165
if (developerPortalType === DeveloperPortalType.selfHosted) {
7266
this.viewManager.addToast("Warning", WarningBackendUrlMissing);
73-
}
67+
}
7468
}
7569

7670
try {

src/config.design.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
{
2-
"environment": "development",
3-
"subscriptionId": "< subscription ID >",
4-
"resourceGroupName": "< resource froup name >",
5-
"serviceName": "< service name >",
6-
"useHipCaptcha": false
1+
{
2+
"environment": "development",
3+
"useHipCaptcha": false,
4+
"armEndpoint": "< armEndpoint >",
5+
"aadClientId": "< aadClientId >",
6+
"aadLoginRequest": "< aadLoginRequest >",
7+
"aadAuthority": "< aadAuthority >"
78
}

src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ export enum SettingNames {
253253
subscriptionId ="subscriptionId",
254254
resourceGroupName = "resourceGroupName",
255255
serviceName = "serviceName",
256+
aadClientId = "aadClientId",
257+
aadAuthority = "aadAuthority",
258+
aadLoginRequest = "aadLoginRequest",
256259
}
257260

258261
export enum DeveloperPortalType {

src/services/armService.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { HttpClient } from "@paperbits/common/http";
44
import { IAuthenticator } from "../authentication/IAuthenticator";
55
import { KnownHttpHeaders } from "../models/knownHttpHeaders";
66
import { ServiceDescriptionContract } from "../contracts/service";
7+
import { SettingNames } from "../constants";
8+
9+
const sessionLoaded = "ArmSessionLoaded";
710

811
export class AzureResourceManagementService {
912
constructor(
@@ -18,7 +21,7 @@ export class AzureResourceManagementService {
1821
public async getServiceDescription(): Promise<ServiceDescriptionContract> {
1922
const managementApiUrl = await this.settingsProvider.getSetting<string>(Constants.SettingNames.managementApiUrl);
2023
const armAccessToken = await this.authenticator.getAccessTokenAsString();
21-
24+
2225
const serviceDescriptionResponse = await this.httpClient.send<ServiceDescriptionContract>({
2326
url: `${managementApiUrl}?api-version=${Constants.managementApiVersion}`,
2427
headers: [{
@@ -61,4 +64,39 @@ export class AzureResourceManagementService {
6164

6265
return userTokenValue;
6366
}
67+
68+
public async loadSessionSettings(): Promise<void> {
69+
const url = new URL(location.href);
70+
const subscriptionId = this.getStoredSetting(url, SettingNames.subscriptionId);
71+
const resourceGroupName = this.getStoredSetting(url, SettingNames.resourceGroupName);
72+
const serviceName = this.getStoredSetting(url, SettingNames.serviceName);
73+
74+
const settings = await this.settingsProvider.getSettings();
75+
const armEndpoint = this.getStoredSetting(url, SettingNames.armEndpoint) || settings[SettingNames.armEndpoint];
76+
77+
if (!subscriptionId || !resourceGroupName || !serviceName || !armEndpoint) {
78+
throw new Error("Required service parameters (like subscription, resource group, service name) were not provided to start editor");
79+
}
80+
81+
if (subscriptionId && resourceGroupName && serviceName) {
82+
const managementApiUrl = `https://${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${serviceName}`;
83+
await this.settingsProvider.setSetting(SettingNames.managementApiUrl, managementApiUrl);
84+
await this.settingsProvider.setSetting(SettingNames.backendUrl, `https://${serviceName}.developer.azure-api/net`);
85+
if(!sessionStorage.getItem(sessionLoaded)) {
86+
sessionStorage.setItem(sessionLoaded, "true");
87+
location.href = location.origin + location.pathname;
88+
}
89+
}
90+
}
91+
92+
private getStoredSetting(url: URL, settingName: string): string {
93+
let settingValue = url.searchParams.get(settingName);
94+
if (settingValue) {
95+
settingValue = decodeURIComponent(settingValue);
96+
sessionStorage.setItem(settingName, settingValue);
97+
} else {
98+
settingValue = sessionStorage.getItem(settingName);
99+
}
100+
return settingValue;
101+
}
64102
}

0 commit comments

Comments
 (0)