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' ;
4
3
5
4
interface AccessTokenRequestOptions {
6
5
scopes : string [ ] ;
@@ -52,34 +51,53 @@ interface AuthorizeServiceConfiguration extends Msal.Configuration {
52
51
}
53
52
54
53
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 ;
57
57
58
58
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
+ }
59
69
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
+ }
64
74
65
- // This promise will only resolve in callback-paths, which is where we check it.
66
- this . _callbackPromise = this . createCallbackResult ( callbackUrl ) ;
75
+ return null ;
67
76
}
68
77
69
78
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 ;
72
92
}
73
93
74
94
async getAccessToken ( request ?: AccessTokenRequestOptions ) : Promise < AccessTokenResult > {
75
95
try {
76
96
const newToken = await this . getTokenCore ( request ?. scopes ) ;
77
-
78
97
return {
79
98
status : AccessTokenResultStatus . Success ,
80
99
token : newToken
81
100
} ;
82
-
83
101
} catch ( e ) {
84
102
return {
85
103
status : AccessTokenResultStatus . RequiresRedirect
@@ -88,12 +106,18 @@ class MsalAuthorizeService implements AuthorizeService {
88
106
}
89
107
90
108
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 ,
93
117
scopes : scopes || this . _settings . defaultAccessTokenScopes
94
118
} ;
95
119
96
- const response = await this . _msalApplication . acquireTokenSilent ( tokenScopes ) ;
120
+ const response = await this . _msalApplication . acquireTokenSilent ( silentRequest ) ;
97
121
return {
98
122
value : response . accessToken ,
99
123
grantedScopes : response . scopes ,
@@ -106,9 +130,10 @@ class MsalAuthorizeService implements AuthorizeService {
106
130
// Before we start any sign-in flow, clear out any previous state so that it doesn't pile up.
107
131
this . purgeState ( ) ;
108
132
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 : [ ]
112
137
} ;
113
138
114
139
if ( this . _settings . defaultAccessTokenScopes && this . _settings . defaultAccessTokenScopes . length > 0 ) {
@@ -130,7 +155,16 @@ class MsalAuthorizeService implements AuthorizeService {
130
155
if ( this . _settings . defaultAccessTokenScopes ?. length > 0 ) {
131
156
// This provisions the token as part of the sign-in flow eagerly so that is already in the cache
132
157
// 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 ) ;
134
168
}
135
169
} catch ( e ) {
136
170
return this . error ( e . errorMessage ) ;
@@ -142,33 +176,45 @@ class MsalAuthorizeService implements AuthorizeService {
142
176
}
143
177
}
144
178
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 ) ;
152
183
} 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 ;
166
205
}
167
206
}
168
207
}
169
208
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 ( ) ;
172
218
}
173
219
174
220
async signOut ( state : any ) {
@@ -241,7 +287,7 @@ class MsalAuthorizeService implements AuthorizeService {
241
287
// msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state.
242
288
// The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format
243
289
// simple uses <<base64urlIdentifier>>.
244
- const appState = ! isLogout ? this . _msalApplication . getAccountState ( state [ 0 ] ) : state [ 0 ] ;
290
+ const appState = ! isLogout ? this . getAccountState ( state [ 0 ] ) : state [ 0 ] ;
245
291
const stateKey = `${ AuthenticationService . _infrastructureKey } .AuthorizeService.${ appState } ` ;
246
292
const stateString = sessionStorage . getItem ( stateKey ) ;
247
293
if ( stateString ) {
@@ -262,37 +308,35 @@ class MsalAuthorizeService implements AuthorizeService {
262
308
}
263
309
}
264
310
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 ) ;
285
317
} else {
286
- const state = await this . retrieveState ( callbackUrl ) ;
287
- return this . success ( state ) ;
318
+ return this . error ( error ) ;
288
319
}
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 ) ;
294
337
}
295
338
}
339
+ return state ;
296
340
}
297
341
298
342
private isMsalError ( resultOrError : any ) : resultOrError is Msal . AuthError {
@@ -319,14 +363,15 @@ class MsalAuthorizeService implements AuthorizeService {
319
363
export class AuthenticationService {
320
364
321
365
static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal' ;
322
- static _initialized = false ;
366
+ static _initialized : Promise < void > ;
323
367
static instance : MsalAuthorizeService ;
324
368
325
369
public static async init ( settings : AuthorizeServiceConfiguration ) {
326
370
if ( ! AuthenticationService . _initialized ) {
327
- AuthenticationService . _initialized = true ;
328
371
AuthenticationService . instance = new MsalAuthorizeService ( settings ) ;
372
+ AuthenticationService . _initialized = AuthenticationService . instance . initializeMsalHandler ( ) ;
329
373
}
374
+ return AuthenticationService . _initialized ;
330
375
}
331
376
332
377
public static getUser ( ) {
0 commit comments