Skip to content

Commit ecf2a23

Browse files
authored
Update to @azure/[email protected] (#24827)
* Update to @azure/[email protected] * Retain promise during initialization * Set knownAuthority hostname from authority URL * Add KnownAuthorities config option and fix silent sign-in * Set knownAuthorities default to empty list
1 parent 3e8c5c4 commit ecf2a23

File tree

4 files changed

+152
-82
lines changed

4 files changed

+152
-82
lines changed

src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts

Lines changed: 119 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import * as Msal from 'msal';
2-
import { StringDict } from 'msal/lib-commonjs/MsalTypes';
3-
import { ClientAuthErrorMessage } from 'msal/lib-commonjs/error/ClientAuthError';
1+
import * as Msal from '@azure/msal-browser';
2+
import { StringDict } from '@azure/msal-common';
43

54
interface AccessTokenRequestOptions {
65
scopes: string[];
@@ -52,34 +51,53 @@ interface AuthorizeServiceConfiguration extends Msal.Configuration {
5251
}
5352

5453
class MsalAuthorizeService implements AuthorizeService {
55-
readonly _msalApplication: Msal.UserAgentApplication;
56-
readonly _callbackPromise: Promise<AuthenticationResult>;
54+
private readonly _msalApplication: Msal.PublicClientApplication;
55+
private _account: Msal.AccountInfo | undefined;
56+
private _redirectCallback: Promise<AuthenticationResult | null> | undefined;
5757

5858
constructor(private readonly _settings: AuthorizeServiceConfiguration) {
59+
if (this._settings.auth?.knownAuthorities?.length == 0) {
60+
this._settings.auth.knownAuthorities = [new URL(this._settings.auth.authority!).hostname]
61+
}
62+
this._msalApplication = new Msal.PublicClientApplication(this._settings);
63+
}
64+
65+
getAccount() {
66+
if (this._account) {
67+
return this._account;
68+
}
5969

60-
// It is important that we capture the callback-url here as msal will remove the auth parameters
61-
// from the url as soon as it gets initialized.
62-
const callbackUrl = location.href;
63-
this._msalApplication = new Msal.UserAgentApplication(this._settings);
70+
const accounts = this._msalApplication.getAllAccounts();
71+
if (accounts && accounts.length) {
72+
return accounts[0];
73+
}
6474

65-
// This promise will only resolve in callback-paths, which is where we check it.
66-
this._callbackPromise = this.createCallbackResult(callbackUrl);
75+
return null;
6776
}
6877

6978
async getUser() {
70-
const account = this._msalApplication.getAccount();
71-
return account?.idTokenClaims;
79+
const account = this.getAccount();
80+
if (!account) {
81+
return;
82+
}
83+
84+
const silentRequest = {
85+
redirectUri: this._settings.auth?.redirectUri,
86+
account: account,
87+
scopes: this._settings.defaultAccessTokenScopes
88+
};
89+
90+
const response = await this._msalApplication.acquireTokenSilent(silentRequest);
91+
return response.idTokenClaims;
7292
}
7393

7494
async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> {
7595
try {
7696
const newToken = await this.getTokenCore(request?.scopes);
77-
7897
return {
7998
status: AccessTokenResultStatus.Success,
8099
token: newToken
81100
};
82-
83101
} catch (e) {
84102
return {
85103
status: AccessTokenResultStatus.RequiresRedirect
@@ -88,12 +106,18 @@ class MsalAuthorizeService implements AuthorizeService {
88106
}
89107

90108
async getTokenCore(scopes?: string[]): Promise<AccessToken | undefined> {
91-
const tokenScopes = {
92-
redirectUri: this._settings.auth.redirectUri as string,
109+
const account = this.getAccount();
110+
if (!account) {
111+
return;
112+
}
113+
114+
const silentRequest = {
115+
redirectUri: this._settings.auth?.redirectUri,
116+
account: account,
93117
scopes: scopes || this._settings.defaultAccessTokenScopes
94118
};
95119

96-
const response = await this._msalApplication.acquireTokenSilent(tokenScopes);
120+
const response = await this._msalApplication.acquireTokenSilent(silentRequest);
97121
return {
98122
value: response.accessToken,
99123
grantedScopes: response.scopes,
@@ -106,9 +130,10 @@ class MsalAuthorizeService implements AuthorizeService {
106130
// Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
107131
this.purgeState();
108132

109-
const request: Msal.AuthenticationParameters = {
110-
redirectUri: this._settings.auth.redirectUri as string,
111-
state: await this.saveState(state)
133+
const request: Msal.AuthorizationUrlRequest = {
134+
redirectUri: this._settings.auth?.redirectUri,
135+
state: await this.saveState(state),
136+
scopes: []
112137
};
113138

114139
if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) {
@@ -130,7 +155,16 @@ class MsalAuthorizeService implements AuthorizeService {
130155
if (this._settings.defaultAccessTokenScopes?.length > 0) {
131156
// This provisions the token as part of the sign-in flow eagerly so that is already in the cache
132157
// when the app asks for it.
133-
await this._msalApplication.acquireTokenSilent(request);
158+
const account = this.getAccount();
159+
if (!account) {
160+
return this.error("No account to get tokens for.");
161+
}
162+
const silentRequest = {
163+
redirectUri: request.redirectUri,
164+
account: account,
165+
scopes: request.scopes,
166+
};
167+
await this._msalApplication.acquireTokenSilent(silentRequest);
134168
}
135169
} catch (e) {
136170
return this.error(e.errorMessage);
@@ -142,33 +176,45 @@ class MsalAuthorizeService implements AuthorizeService {
142176
}
143177
}
144178

145-
async signInCore(request: Msal.AuthenticationParameters): Promise<Msal.AuthResponse | Msal.AuthError | undefined> {
146-
if (this._settings.loginMode.toLowerCase() === "redirect") {
147-
try {
148-
this._msalApplication.loginRedirect(request);
149-
} catch (e) {
150-
return e;
151-
}
179+
async signInCore(request: Msal.AuthorizationUrlRequest): Promise<Msal.AuthenticationResult | Msal.AuthError | undefined> {
180+
const loginMode = this._settings.loginMode.toLowerCase();
181+
if (loginMode === 'redirect') {
182+
return this.signInWithRedirect(request);
152183
} else {
153-
try {
154-
return await this._msalApplication.loginPopup(request);
155-
} catch (e) {
156-
// If the user explicitly cancelled the pop-up, avoid performing a redirect.
157-
if (this.isMsalError(e) && e.errorCode !== ClientAuthErrorMessage.userCancelledError.code) {
158-
try {
159-
this._msalApplication.loginRedirect(request);
160-
} catch (e) {
161-
return e;
162-
}
163-
} else {
164-
return e;
165-
}
184+
return this.signInWithPopup(request);
185+
}
186+
}
187+
188+
private async signInWithRedirect(request: Msal.RedirectRequest) {
189+
try {
190+
return await this._msalApplication.loginRedirect(request);
191+
} catch (e) {
192+
return e;
193+
}
194+
}
195+
196+
private async signInWithPopup(request: Msal.PopupRequest) {
197+
try {
198+
return await this._msalApplication.loginPopup(request);
199+
} catch (e) {
200+
// If the user explicitly cancelled the pop-up, avoid performing a redirect.
201+
if (this.isMsalError(e) && e.errorCode !== Msal.BrowserAuthErrorMessage.userCancelledError.code) {
202+
this.signInWithRedirect(request);
203+
} else {
204+
return e;
166205
}
167206
}
168207
}
169208

170-
completeSignIn() {
171-
return this._callbackPromise;
209+
async completeSignIn() {
210+
// Make sure that the redirect handler has completed execution before
211+
// completing sign in.
212+
await this._redirectCallback;
213+
const account = this.getAccount();
214+
if (account) {
215+
return this.success(account);
216+
}
217+
return this.operationCompleted();
172218
}
173219

174220
async signOut(state: any) {
@@ -241,7 +287,7 @@ class MsalAuthorizeService implements AuthorizeService {
241287
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
242288
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
243289
// simple uses <<base64urlIdentifier>>.
244-
const appState = !isLogout ? this._msalApplication.getAccountState(state[0]) : state[0];
290+
const appState = !isLogout ? this.getAccountState(state[0]) : state[0];
245291
const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`;
246292
const stateString = sessionStorage.getItem(stateKey);
247293
if (stateString) {
@@ -262,37 +308,35 @@ class MsalAuthorizeService implements AuthorizeService {
262308
}
263309
}
264310

265-
private async createCallbackResult(callbackUrl: string): Promise<AuthenticationResult> {
266-
// msal.js requires a callback to be registered during app initialization to handle redirect flows.
267-
// To map that behavior to our API we register a callback early and store the result of that callback
268-
// as a promise on an instance field to be able to serve the state back to the main app.
269-
const promiseFactory = (resolve: (result: Msal.AuthResponse) => void, reject: (error: Msal.AuthError) => void): void => {
270-
this._msalApplication.handleRedirectCallback(
271-
authenticationResponse => {
272-
resolve(authenticationResponse);
273-
},
274-
authenticationError => {
275-
reject(authenticationError);
276-
});
277-
}
278-
279-
try {
280-
// Evaluate the promise to capture any authentication errors
281-
await new Promise<Msal.AuthResponse>(promiseFactory);
282-
// See https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q6-how-to-avoid-page-reloads-when-acquiring-and-renewing-tokens-silently
283-
if (window !== window.parent && !window.opener) {
284-
return this.operationCompleted();
311+
async initializeMsalHandler() {
312+
this._redirectCallback = this._msalApplication.handleRedirectPromise().then(
313+
(result: Msal.AuthenticationResult | null) => this.handleResult(result)
314+
).catch((error: any) => {
315+
if (this.isMsalError(error)) {
316+
return this.error(error.errorMessage);
285317
} else {
286-
const state = await this.retrieveState(callbackUrl);
287-
return this.success(state);
318+
return this.error(error);
288319
}
289-
} catch (e) {
290-
if (this.isMsalError(e)) {
291-
return this.error(e.errorMessage);
292-
} else {
293-
return this.error(e);
320+
})
321+
}
322+
323+
private handleResult(result: Msal.AuthenticationResult | null) {
324+
if (result != null) {
325+
this._account = result.account;
326+
return this.success(result.state);
327+
} else {
328+
return this.operationCompleted();
329+
}
330+
}
331+
332+
private getAccountState(state: string) {
333+
if (state) {
334+
const splitIndex = state.indexOf("|");
335+
if (splitIndex > -1 && splitIndex + 1 < state.length) {
336+
return state.substring(splitIndex + 1);
294337
}
295338
}
339+
return state;
296340
}
297341

298342
private isMsalError(resultOrError: any): resultOrError is Msal.AuthError {
@@ -319,14 +363,15 @@ class MsalAuthorizeService implements AuthorizeService {
319363
export class AuthenticationService {
320364

321365
static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal';
322-
static _initialized = false;
366+
static _initialized: Promise<void>;
323367
static instance: MsalAuthorizeService;
324368

325369
public static async init(settings: AuthorizeServiceConfiguration) {
326370
if (!AuthenticationService._initialized) {
327-
AuthenticationService._initialized = true;
328371
AuthenticationService.instance = new MsalAuthorizeService(settings);
372+
AuthenticationService._initialized = AuthenticationService.instance.initializeMsalHandler();
329373
}
374+
return AuthenticationService._initialized;
330375
}
331376

332377
public static getUser() {

src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
"webpack-cli": "^3.3.10"
1414
},
1515
"dependencies": {
16-
"msal": "^1.2.1"
16+
"@azure/msal-browser": "^2.0.0"
1717
}
1818
}

src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
# yarn lockfile v1
33

44

5+
"@azure/msal-browser@^2.0.0":
6+
version "2.0.0"
7+
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.0.0.tgz#09eb3ed2112bdcd11c751f1c0b9cec588b49b8c6"
8+
integrity sha512-0L4XksaXmtl870bQTPxbHCkxMEMmSbsgkkVpb6bvXg8ngOLWnkxvV6tstj84JtQHcJPjNYkYY41jgBQxgC4/KQ==
9+
dependencies:
10+
"@azure/msal-common" "1.0.0"
11+
12+
13+
version "1.0.0"
14+
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-1.0.0.tgz#421f4859e6cb68cfacb03bb2c6c873efdffc76f0"
15+
integrity sha512-l/+1Z9kQWLAlMwJ/c3MGhy4ujtEAK/3CMUaUXHvjUIsQknLFRb9+b3id5YSuToPfAvdUkAQGDZiQXosv1I+eLA==
16+
dependencies:
17+
debug "^4.1.1"
18+
519
"@webassemblyjs/[email protected]":
620
version "1.8.5"
721
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@@ -690,6 +704,13 @@ debug@^2.2.0, debug@^2.3.3:
690704
dependencies:
691705
ms "2.0.0"
692706

707+
debug@^4.1.1:
708+
version "4.1.1"
709+
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
710+
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
711+
dependencies:
712+
ms "^2.1.1"
713+
693714
decamelize@^1.2.0:
694715
version "1.2.0"
695716
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1667,12 +1688,10 @@ [email protected]:
16671688
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
16681689
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
16691690

1670-
msal@^1.2.1:
1671-
version "1.2.1"
1672-
resolved "https://registry.yarnpkg.com/msal/-/msal-1.2.1.tgz#08133e37ab0b9741866c89a3fadc55aadb980723"
1673-
integrity sha512-Zo28eyRtT/Un+zcpMfPtTPD+eo/OqzsRER0k5dyk8Mje/K1oLlaEOAgZHlJs59Y2xyuVg8OrcKqSn/1MeNjZYw==
1674-
dependencies:
1675-
tslib "^1.9.3"
1691+
ms@^2.1.1:
1692+
version "2.1.2"
1693+
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
1694+
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
16761695

16771696
nan@^2.12.1:
16781697
version "2.14.0"
@@ -2485,7 +2504,7 @@ ts-loader@^6.2.1:
24852504
micromatch "^4.0.0"
24862505
semver "^6.0.0"
24872506

2488-
tslib@^1.9.0, tslib@^1.9.3:
2507+
tslib@^1.9.0:
24892508
version "1.10.0"
24902509
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
24912510
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==

src/Components/WebAssembly/Authentication.Msal/src/Models/MsalAuthenticationOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56

67
namespace Microsoft.Authentication.WebAssembly.Msal
78
{
@@ -48,5 +49,10 @@ public class MsalAuthenticationOptions
4849
/// Gets or sets whether or not to navigate to the login request url after a successful login.
4950
/// </summary>
5051
public bool NavigateToLoginRequestUrl { get; set; } = false;
52+
53+
/// <summary>
54+
/// Gets or sets the set of known authority host names for the application.
55+
/// </summary>
56+
public IList<string> KnownAuthorities { get; set; } = new List<string>();
5157
}
5258
}

0 commit comments

Comments
 (0)