@@ -10,6 +10,7 @@ interface AppServicesToken {
1010 id_token : string ;
1111 access_token : string ;
1212 user_claims : Record < string , any > ;
13+ expires_on : string ;
1314}
1415
1516interface AuthSetup {
@@ -92,29 +93,66 @@ export const getRedirectUri = () => {
9293 return window . location . origin + authSetup . msalConfig . auth . redirectUri ;
9394} ;
9495
95- // Get an access token if a user logged in using app services authentication
96- // Returns null if the app doesn't support app services authentication
96+ // Cache the app services token if it's available
97+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#global_context
98+ declare global {
99+ var cachedAppServicesToken : AppServicesToken | null ;
100+ }
101+ globalThis . cachedAppServicesToken = null ;
102+
103+ /**
104+ * Retrieves an access token if the user is logged in using app services authentication.
105+ * Checks if the current token is expired and fetches a new token if necessary.
106+ * Returns null if the app doesn't support app services authentication.
107+ *
108+ * @returns {Promise<AppServicesToken | null> } A promise that resolves to an AppServicesToken if the user is authenticated, or null if authentication is not supported or fails.
109+ */
97110const getAppServicesToken = ( ) : Promise < AppServicesToken | null > => {
98- return fetch ( appServicesAuthTokenRefreshUrl ) . then ( r => {
99- if ( r . ok ) {
100- return fetch ( appServicesAuthTokenUrl ) . then ( r => {
111+ const checkNotExpired = ( appServicesToken : AppServicesToken ) => {
112+ const currentDate = new Date ( ) ;
113+ const expiresOnDate = new Date ( appServicesToken . expires_on ) ;
114+ return expiresOnDate > currentDate ;
115+ } ;
116+
117+ if ( globalThis . cachedAppServicesToken && checkNotExpired ( globalThis . cachedAppServicesToken ) ) {
118+ return Promise . resolve ( globalThis . cachedAppServicesToken ) ;
119+ }
120+
121+ const getAppServicesTokenFromMe : ( ) => Promise < AppServicesToken | null > = ( ) => {
122+ return fetch ( appServicesAuthTokenUrl ) . then ( r => {
123+ if ( r . ok ) {
124+ return r . json ( ) . then ( json => {
125+ if ( json . length > 0 ) {
126+ return {
127+ id_token : json [ 0 ] [ "id_token" ] as string ,
128+ access_token : json [ 0 ] [ "access_token" ] as string ,
129+ user_claims : json [ 0 ] [ "user_claims" ] . reduce ( ( acc : Record < string , any > , item : Record < string , any > ) => {
130+ acc [ item . typ ] = item . val ;
131+ return acc ;
132+ } , { } ) as Record < string , any > ,
133+ expires_on : json [ 0 ] [ "expires_on" ] as string
134+ } as AppServicesToken ;
135+ }
136+
137+ return null ;
138+ } ) ;
139+ }
140+
141+ return null ;
142+ } ) ;
143+ } ;
144+
145+ return getAppServicesTokenFromMe ( ) . then ( token => {
146+ if ( token ) {
147+ if ( checkNotExpired ( token ) ) {
148+ globalThis . cachedAppServicesToken = token ;
149+ return token ;
150+ }
151+
152+ return fetch ( appServicesAuthTokenRefreshUrl ) . then ( r => {
101153 if ( r . ok ) {
102- return r . json ( ) . then ( json => {
103- if ( json . length > 0 ) {
104- return {
105- id_token : json [ 0 ] [ "id_token" ] as string ,
106- access_token : json [ 0 ] [ "access_token" ] as string ,
107- user_claims : json [ 0 ] [ "user_claims" ] . reduce ( ( acc : Record < string , any > , item : Record < string , any > ) => {
108- acc [ item . typ ] = item . val ;
109- return acc ;
110- } , { } ) as Record < string , any >
111- } ;
112- }
113-
114- return null ;
115- } ) ;
154+ return getAppServicesTokenFromMe ( ) ;
116155 }
117-
118156 return null ;
119157 } ) ;
120158 }
@@ -123,24 +161,40 @@ const getAppServicesToken = (): Promise<AppServicesToken | null> => {
123161 } ) ;
124162} ;
125163
126- export const appServicesToken = await getAppServicesToken ( ) ;
164+ export const isUsingAppServicesLogin = ( await getAppServicesToken ( ) ) != null ;
127165
128166// Sign out of app services
129167// Learn more at https://learn.microsoft.com/azure/app-service/configure-authentication-customize-sign-in-out#sign-out-of-a-session
130168export const appServicesLogout = ( ) => {
131169 window . location . href = appServicesAuthLogoutUrl ;
132170} ;
133171
134- // Determine if the user is logged in
135- // The user may have logged in either using the app services login or the on-page login
136- export const isLoggedIn = ( client : IPublicClientApplication | undefined ) : boolean => {
137- return client ?. getActiveAccount ( ) != null || appServicesToken != null ;
172+ /**
173+ * Determines if the user is logged in either via the MSAL public client application or the app services login.
174+ * @param {IPublicClientApplication | undefined } client - The MSAL public client application instance, or undefined if not available.
175+ * @returns {Promise<boolean> } A promise that resolves to true if the user is logged in, false otherwise.
176+ */
177+ export const checkLoggedIn = async ( client : IPublicClientApplication | undefined ) : Promise < boolean > => {
178+ if ( client ) {
179+ const activeAccount = client . getActiveAccount ( ) ;
180+ if ( activeAccount ) {
181+ return true ;
182+ }
183+ }
184+
185+ const appServicesToken = await getAppServicesToken ( ) ;
186+ if ( appServicesToken ) {
187+ return true ;
188+ }
189+
190+ return false ;
138191} ;
139192
140193// Get an access token for use with the API server.
141194// ID token received when logging in may not be used for this purpose because it has the incorrect audience
142195// Use the access token from app services login if available
143- export const getToken = ( client : IPublicClientApplication ) : Promise < string | undefined > => {
196+ export const getToken = async ( client : IPublicClientApplication ) : Promise < string | undefined > => {
197+ const appServicesToken = await getAppServicesToken ( ) ;
144198 if ( appServicesToken ) {
145199 return Promise . resolve ( appServicesToken . access_token ) ;
146200 }
@@ -156,3 +210,43 @@ export const getToken = (client: IPublicClientApplication): Promise<string | und
156210 return undefined ;
157211 } ) ;
158212} ;
213+
214+ /**
215+ * Retrieves the username of the active account.
216+ * If no active account is found, attempts to retrieve the username from the app services login token if available.
217+ * @param {IPublicClientApplication } client - The MSAL public client application instance.
218+ * @returns {Promise<string | null> } The username of the active account, or null if no username is found.
219+ */
220+ export const getUsername = async ( client : IPublicClientApplication ) : Promise < string | null > => {
221+ const activeAccount = client . getActiveAccount ( ) ;
222+ if ( activeAccount ) {
223+ return activeAccount . username ;
224+ }
225+
226+ const appServicesToken = await getAppServicesToken ( ) ;
227+ if ( appServicesToken ?. user_claims ) {
228+ return appServicesToken . user_claims . preferred_username ;
229+ }
230+
231+ return null ;
232+ } ;
233+
234+ /**
235+ * Retrieves the token claims of the active account.
236+ * If no active account is found, attempts to retrieve the token claims from the app services login token if available.
237+ * @param {IPublicClientApplication } client - The MSAL public client application instance.
238+ * @returns {Promise<Record<string, unknown> | undefined> } A promise that resolves to the token claims of the active account, the user claims from the app services login token, or undefined if no claims are found.
239+ */
240+ export const getTokenClaims = async ( client : IPublicClientApplication ) : Promise < Record < string , unknown > | undefined > => {
241+ const activeAccount = client . getActiveAccount ( ) ;
242+ if ( activeAccount ) {
243+ return activeAccount . idTokenClaims ;
244+ }
245+
246+ const appServicesToken = await getAppServicesToken ( ) ;
247+ if ( appServicesToken ) {
248+ return appServicesToken . user_claims ;
249+ }
250+
251+ return undefined ;
252+ } ;
0 commit comments