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
54interface AccessTokenRequestOptions {
65 scopes : string [ ] ;
@@ -52,34 +51,53 @@ interface AuthorizeServiceConfiguration extends Msal.Configuration {
5251}
5352
5453class 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 {
319363export 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 ( ) {
0 commit comments