@@ -10,6 +10,7 @@ interface AppServicesToken {
10
10
id_token : string ;
11
11
access_token : string ;
12
12
user_claims : Record < string , any > ;
13
+ expires_on : string ;
13
14
}
14
15
15
16
interface AuthSetup {
@@ -92,29 +93,66 @@ export const getRedirectUri = () => {
92
93
return window . location . origin + authSetup . msalConfig . auth . redirectUri ;
93
94
} ;
94
95
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
+ */
97
110
const 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 => {
101
153
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 ( ) ;
116
155
}
117
-
118
156
return null ;
119
157
} ) ;
120
158
}
@@ -123,24 +161,40 @@ const getAppServicesToken = (): Promise<AppServicesToken | null> => {
123
161
} ) ;
124
162
} ;
125
163
126
- export const appServicesToken = await getAppServicesToken ( ) ;
164
+ export const isUsingAppServicesLogin = ( await getAppServicesToken ( ) ) != null ;
127
165
128
166
// Sign out of app services
129
167
// Learn more at https://learn.microsoft.com/azure/app-service/configure-authentication-customize-sign-in-out#sign-out-of-a-session
130
168
export const appServicesLogout = ( ) => {
131
169
window . location . href = appServicesAuthLogoutUrl ;
132
170
} ;
133
171
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 ;
138
191
} ;
139
192
140
193
// Get an access token for use with the API server.
141
194
// ID token received when logging in may not be used for this purpose because it has the incorrect audience
142
195
// 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 ( ) ;
144
198
if ( appServicesToken ) {
145
199
return Promise . resolve ( appServicesToken . access_token ) ;
146
200
}
@@ -156,3 +210,43 @@ export const getToken = (client: IPublicClientApplication): Promise<string | und
156
210
return undefined ;
157
211
} ) ;
158
212
} ;
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