@azure/msal-react 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@azure/msal-react",
3
3
  "entries": [
4
+ {
5
+ "date": "Tue, 08 Feb 2022 00:41:07 GMT",
6
+ "tag": "@azure/msal-react_v1.3.0",
7
+ "version": "1.3.0",
8
+ "comments": {
9
+ "minor": [
10
+ {
11
+ "author": "thomas.norling@microsoft.com",
12
+ "package": "@azure/msal-react",
13
+ "commit": "67ce881faa2a6931320541d99de2efe4e9ba6a3f",
14
+ "comment": "useMsalAuthentication hook acquires a token if user is already signed in #4280"
15
+ },
16
+ {
17
+ "author": "beachball",
18
+ "package": "@azure/msal-react",
19
+ "comment": "Bump @azure/msal-browser to v2.22.0",
20
+ "commit": "639253acbc825e1f19ca712bc72dce8da149e3e8"
21
+ }
22
+ ],
23
+ "none": [
24
+ {
25
+ "author": "kamausamuel11@gmail.com",
26
+ "package": "@azure/msal-react",
27
+ "commit": "5767008f8ef9f3b05aeba421cc4aa6c6bb616e86",
28
+ "comment": "fix: update package-lock files"
29
+ },
30
+ {
31
+ "author": "thomas.norling@microsoft.com",
32
+ "package": "@azure/msal-react",
33
+ "commit": "19f76b1d9cd2b0f1a4a7e4765c8ee9e7e17f3d59",
34
+ "comment": "Test changes #4383"
35
+ }
36
+ ]
37
+ }
38
+ },
4
39
  {
5
40
  "date": "Tue, 04 Jan 2022 00:20:29 GMT",
6
41
  "tag": "@azure/msal-react_v1.2.0",
package/CHANGELOG.md CHANGED
@@ -1,9 +1,18 @@
1
1
  # Change Log - @azure/msal-react
2
2
 
3
- This log was last generated on Tue, 04 Jan 2022 00:20:29 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 08 Feb 2022 00:41:07 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 1.3.0
8
+
9
+ Tue, 08 Feb 2022 00:41:07 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - useMsalAuthentication hook acquires a token if user is already signed in #4280 (thomas.norling@microsoft.com)
14
+ - Bump @azure/msal-browser to v2.22.0
15
+
7
16
  ## 1.2.0
8
17
 
9
18
  Tue, 04 Jan 2022 00:20:29 GMT
@@ -0,0 +1,16 @@
1
+ import { AuthError } from "@azure/msal-browser";
2
+ export declare const ReactAuthErrorMessage: {
3
+ invalidInteractionType: {
4
+ code: string;
5
+ desc: string;
6
+ };
7
+ unableToFallbackToInteraction: {
8
+ code: string;
9
+ desc: string;
10
+ };
11
+ };
12
+ export declare class ReactAuthError extends AuthError {
13
+ constructor(errorCode: string, errorMessage?: string);
14
+ static createInvalidInteractionTypeError(): ReactAuthError;
15
+ static createUnableToFallbackToInteractionError(): ReactAuthError;
16
+ }
@@ -4,4 +4,4 @@ import { AccountIdentifiers } from "../types/AccountIdentifiers";
4
4
  * Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in
5
5
  * @param accountIdentifiers
6
6
  */
7
- export declare function useAccount(accountIdentifiers: AccountIdentifiers): AccountInfo | null;
7
+ export declare function useAccount(accountIdentifiers?: AccountIdentifiers): AccountInfo | null;
@@ -1,13 +1,15 @@
1
- import { PopupRequest, RedirectRequest, SsoSilentRequest, InteractionType, AuthenticationResult, AuthError } from "@azure/msal-browser";
1
+ import { PopupRequest, RedirectRequest, SsoSilentRequest, InteractionType, AuthenticationResult, AuthError, SilentRequest } from "@azure/msal-browser";
2
2
  import { AccountIdentifiers } from "../types/AccountIdentifiers";
3
3
  export declare type MsalAuthenticationResult = {
4
- login: Function;
4
+ login: (callbackInteractionType?: InteractionType | undefined, callbackRequest?: PopupRequest | RedirectRequest | SilentRequest) => Promise<AuthenticationResult | null>;
5
+ acquireToken: (callbackInteractionType?: InteractionType | undefined, callbackRequest?: SilentRequest | undefined) => Promise<AuthenticationResult | null>;
5
6
  result: AuthenticationResult | null;
6
7
  error: AuthError | null;
7
8
  };
8
9
  /**
9
- * Invokes a login call if a user is not currently signed-in. Failed logins can be retried using the login callback returned.
10
- * Optionally provide a request object to be used in the login call.
10
+ * If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned.
11
+ * If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned.
12
+ * Optionally provide a request object to be used in the login/acquireToken call.
11
13
  * Optionally provide a specific user that should be logged in.
12
14
  * @param interactionType
13
15
  * @param authenticationRequest
@@ -60,10 +60,32 @@ function accountArraysAreEqual(arrayA, arrayB) {
60
60
  return elementA.homeAccountId === elementB.homeAccountId && elementA.localAccountId === elementB.localAccountId && elementA.username === elementB.username;
61
61
  });
62
62
  }
63
+ function getAccountByIdentifiers(allAccounts, accountIdentifiers) {
64
+ if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) {
65
+ const matchedAccounts = allAccounts.filter(accountObj => {
66
+ if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) {
67
+ return false;
68
+ }
69
+
70
+ if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) {
71
+ return false;
72
+ }
73
+
74
+ if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) {
75
+ return false;
76
+ }
77
+
78
+ return true;
79
+ });
80
+ return matchedAccounts[0] || null;
81
+ } else {
82
+ return null;
83
+ }
84
+ }
63
85
 
64
86
  /* eslint-disable header/header */
65
87
  const name = "@azure/msal-react";
66
- const version = "1.2.0";
88
+ const version = "1.3.0";
67
89
 
68
90
  /*
69
91
  * Copyright (c) Microsoft Corporation. All rights reserved.
@@ -81,7 +103,7 @@ function MsalProvider({
81
103
  return instance.getLogger().clone(name, version);
82
104
  }, [instance]); // State hook to store accounts
83
105
 
84
- const [accounts, setAccounts] = React.useState([]); // State hook to store in progress value
106
+ const [accounts, setAccounts] = React.useState(() => instance.getAllAccounts()); // State hook to store in progress value
85
107
 
86
108
  const [inProgress, setInProgress] = React.useState(msalBrowser.InteractionStatus.Startup); // Mutable object used in the event callback
87
109
 
@@ -177,61 +199,9 @@ const useMsal = () => React.useContext(MsalContext);
177
199
  * Licensed under the MIT License.
178
200
  */
179
201
 
180
- function getAccount(instance, accountIdentifiers) {
181
- const allAccounts = instance.getAllAccounts();
182
-
183
- if (allAccounts.length > 0 && (accountIdentifiers.homeAccountId || accountIdentifiers.localAccountId || accountIdentifiers.username)) {
184
- const matchedAccounts = allAccounts.filter(accountObj => {
185
- if (accountIdentifiers.username && accountIdentifiers.username.toLowerCase() !== accountObj.username.toLowerCase()) {
186
- return false;
187
- }
188
-
189
- if (accountIdentifiers.homeAccountId && accountIdentifiers.homeAccountId.toLowerCase() !== accountObj.homeAccountId.toLowerCase()) {
190
- return false;
191
- }
192
-
193
- if (accountIdentifiers.localAccountId && accountIdentifiers.localAccountId.toLowerCase() !== accountObj.localAccountId.toLowerCase()) {
194
- return false;
195
- }
196
-
197
- return true;
198
- });
199
- return matchedAccounts[0] || null;
200
- } else {
201
- return null;
202
- }
203
- }
204
- /**
205
- * Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in
206
- * @param accountIdentifiers
207
- */
208
-
209
-
210
- function useAccount(accountIdentifiers) {
211
- const {
212
- instance,
213
- inProgress
214
- } = useMsal();
215
- const initialStateValue = inProgress === msalBrowser.InteractionStatus.Startup ? null : getAccount(instance, accountIdentifiers);
216
- const [account, setAccount] = React.useState(initialStateValue);
217
- React.useEffect(() => {
218
- const currentAccount = getAccount(instance, accountIdentifiers);
219
-
220
- if (!msalBrowser.AccountEntity.accountInfoIsEqual(account, currentAccount, true)) {
221
- setAccount(currentAccount);
222
- }
223
- }, [inProgress, accountIdentifiers, instance, account]);
224
- return account;
225
- }
226
-
227
- /*
228
- * Copyright (c) Microsoft Corporation. All rights reserved.
229
- * Licensed under the MIT License.
230
- */
231
-
232
- function isAuthenticated(allAccounts, account, matchAccount) {
202
+ function isAuthenticated(allAccounts, matchAccount) {
233
203
  if (matchAccount && (matchAccount.username || matchAccount.homeAccountId || matchAccount.localAccountId)) {
234
- return !!account;
204
+ return !!getAccountByIdentifiers(allAccounts, matchAccount);
235
205
  }
236
206
 
237
207
  return allAccounts.length > 0;
@@ -244,15 +214,12 @@ function isAuthenticated(allAccounts, account, matchAccount) {
244
214
 
245
215
  function useIsAuthenticated(matchAccount) {
246
216
  const {
247
- accounts: allAccounts,
248
- inProgress
217
+ accounts: allAccounts
249
218
  } = useMsal();
250
- const account = useAccount(matchAccount || {});
251
- const initialStateValue = inProgress === msalBrowser.InteractionStatus.Startup ? false : isAuthenticated(allAccounts, account, matchAccount);
252
- const [hasAuthenticated, setHasAuthenticated] = React.useState(initialStateValue);
219
+ const [hasAuthenticated, setHasAuthenticated] = React.useState(() => isAuthenticated(allAccounts, matchAccount));
253
220
  React.useEffect(() => {
254
- setHasAuthenticated(isAuthenticated(allAccounts, account, matchAccount));
255
- }, [allAccounts, account, matchAccount]);
221
+ setHasAuthenticated(isAuthenticated(allAccounts, matchAccount));
222
+ }, [allAccounts, matchAccount]);
256
223
  return hasAuthenticated;
257
224
  }
258
225
 
@@ -324,9 +291,82 @@ function UnauthenticatedTemplate({
324
291
  * Copyright (c) Microsoft Corporation. All rights reserved.
325
292
  * Licensed under the MIT License.
326
293
  */
294
+
295
+ function getAccount(instance, accountIdentifiers) {
296
+ if (!accountIdentifiers || !accountIdentifiers.homeAccountId && !accountIdentifiers.localAccountId && !accountIdentifiers.username) {
297
+ // If no account identifiers are provided, return active account
298
+ return instance.getActiveAccount();
299
+ }
300
+
301
+ return getAccountByIdentifiers(instance.getAllAccounts(), accountIdentifiers);
302
+ }
327
303
  /**
328
- * Invokes a login call if a user is not currently signed-in. Failed logins can be retried using the login callback returned.
329
- * Optionally provide a request object to be used in the login call.
304
+ * Given 1 or more accountIdentifiers, returns the Account object if the user is signed-in
305
+ * @param accountIdentifiers
306
+ */
307
+
308
+
309
+ function useAccount(accountIdentifiers) {
310
+ const {
311
+ instance,
312
+ inProgress,
313
+ logger
314
+ } = useMsal();
315
+ const [account, setAccount] = React.useState(() => getAccount(instance, accountIdentifiers));
316
+ React.useEffect(() => {
317
+ setAccount(currentAccount => {
318
+ const nextAccount = getAccount(instance, accountIdentifiers);
319
+
320
+ if (!msalBrowser.AccountEntity.accountInfoIsEqual(currentAccount, nextAccount, true)) {
321
+ logger.info("useAccount - Updating account");
322
+ return nextAccount;
323
+ }
324
+
325
+ return currentAccount;
326
+ });
327
+ }, [inProgress, accountIdentifiers, instance, logger]);
328
+ return account;
329
+ }
330
+
331
+ /*
332
+ * Copyright (c) Microsoft Corporation. All rights reserved.
333
+ * Licensed under the MIT License.
334
+ */
335
+ const ReactAuthErrorMessage = {
336
+ invalidInteractionType: {
337
+ code: "invalid_interaction_type",
338
+ desc: "The provided interaction type is invalid."
339
+ },
340
+ unableToFallbackToInteraction: {
341
+ code: "unable_to_fallback_to_interaction",
342
+ desc: "Interaction is required but another interaction is already in progress. Please try again when the current interaction is complete."
343
+ }
344
+ };
345
+ class ReactAuthError extends msalBrowser.AuthError {
346
+ constructor(errorCode, errorMessage) {
347
+ super(errorCode, errorMessage);
348
+ Object.setPrototypeOf(this, ReactAuthError.prototype);
349
+ this.name = "ReactAuthError";
350
+ }
351
+
352
+ static createInvalidInteractionTypeError() {
353
+ return new ReactAuthError(ReactAuthErrorMessage.invalidInteractionType.code, ReactAuthErrorMessage.invalidInteractionType.desc);
354
+ }
355
+
356
+ static createUnableToFallbackToInteractionError() {
357
+ return new ReactAuthError(ReactAuthErrorMessage.unableToFallbackToInteraction.code, ReactAuthErrorMessage.unableToFallbackToInteraction.desc);
358
+ }
359
+
360
+ }
361
+
362
+ /*
363
+ * Copyright (c) Microsoft Corporation. All rights reserved.
364
+ * Licensed under the MIT License.
365
+ */
366
+ /**
367
+ * If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned.
368
+ * If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned.
369
+ * Optionally provide a request object to be used in the login/acquireToken call.
330
370
  * Optionally provide a specific user that should be logged in.
331
371
  * @param interactionType
332
372
  * @param authenticationRequest
@@ -340,8 +380,28 @@ function useMsalAuthentication(interactionType, authenticationRequest, accountId
340
380
  logger
341
381
  } = useMsal();
342
382
  const isAuthenticated = useIsAuthenticated(accountIdentifiers);
343
- const [[result, error], setResponse] = React.useState([null, null]);
344
- const [hasBeenCalled, setHasBeenCalled] = React.useState(false);
383
+ const account = useAccount(accountIdentifiers);
384
+ const [[result, error], setResponse] = React.useState([null, null]); // Boolean used to check if interaction is in progress in acquireTokenSilent fallback. Use Ref instead of state to prevent acquireToken function from being regenerated on each change to interactionInProgress value
385
+
386
+ const interactionInProgress = React.useRef(inProgress !== msalBrowser.InteractionStatus.None);
387
+ React.useEffect(() => {
388
+ interactionInProgress.current = inProgress !== msalBrowser.InteractionStatus.None;
389
+ }, [inProgress]); // Flag used to control when the hook calls login/acquireToken
390
+
391
+ const shouldAcquireToken = React.useRef(true);
392
+ React.useEffect(() => {
393
+ if (!!error) {
394
+ // Errors should be handled by consuming component
395
+ shouldAcquireToken.current = false;
396
+ return;
397
+ }
398
+
399
+ if (!!result) {
400
+ // Token has already been acquired, consuming component/application is responsible for renewing
401
+ shouldAcquireToken.current = false;
402
+ return;
403
+ }
404
+ }, [error, result]);
345
405
  const login = React.useCallback(async (callbackInteractionType, callbackRequest) => {
346
406
  const loginType = callbackInteractionType || interactionType;
347
407
  const loginRequest = callbackRequest || authenticationRequest;
@@ -361,9 +421,59 @@ function useMsalAuthentication(interactionType, authenticationRequest, accountId
361
421
  return instance.ssoSilent(loginRequest);
362
422
 
363
423
  default:
364
- throw "Invalid interaction type provided.";
424
+ throw ReactAuthError.createInvalidInteractionTypeError();
365
425
  }
366
426
  }, [instance, interactionType, authenticationRequest, logger]);
427
+ const acquireToken = React.useCallback(async (callbackInteractionType, callbackRequest) => {
428
+ const fallbackInteractionType = callbackInteractionType || interactionType;
429
+ let tokenRequest;
430
+
431
+ if (callbackRequest) {
432
+ logger.trace("useMsalAuthentication - acquireToken - Using request provided in the callback");
433
+ tokenRequest = { ...callbackRequest
434
+ };
435
+ } else if (authenticationRequest) {
436
+ logger.trace("useMsalAuthentication - acquireToken - Using request provided in the hook");
437
+ tokenRequest = { ...authenticationRequest,
438
+ scopes: authenticationRequest.scopes || msalBrowser.OIDC_DEFAULT_SCOPES
439
+ };
440
+ } else {
441
+ logger.trace("useMsalAuthentication - acquireToken - No request object provided, using default request.");
442
+ tokenRequest = {
443
+ scopes: msalBrowser.OIDC_DEFAULT_SCOPES
444
+ };
445
+ }
446
+
447
+ if (!tokenRequest.account && account) {
448
+ logger.trace("useMsalAuthentication - acquireToken - Attaching account to request");
449
+ tokenRequest.account = account;
450
+ }
451
+
452
+ const getToken = async () => {
453
+ logger.verbose("useMsalAuthentication - Calling acquireTokenSilent");
454
+ return instance.acquireTokenSilent(tokenRequest).catch(async e => {
455
+ if (e instanceof msalBrowser.InteractionRequiredAuthError) {
456
+ if (!interactionInProgress.current) {
457
+ logger.error("useMsalAuthentication - Interaction required, falling back to interaction");
458
+ return login(fallbackInteractionType, tokenRequest);
459
+ } else {
460
+ logger.error("useMsalAuthentication - Interaction required but is already in progress. Please try again, if needed, after interaction completes.");
461
+ throw ReactAuthError.createUnableToFallbackToInteractionError();
462
+ }
463
+ }
464
+
465
+ throw e;
466
+ });
467
+ };
468
+
469
+ return getToken().then(response => {
470
+ setResponse([response, null]);
471
+ return response;
472
+ }).catch(e => {
473
+ setResponse([null, e]);
474
+ throw e;
475
+ });
476
+ }, [instance, interactionType, authenticationRequest, logger, account, login]);
367
477
  React.useEffect(() => {
368
478
  const callbackId = instance.addEventCallback(message => {
369
479
  switch (message.eventType) {
@@ -393,18 +503,27 @@ function useMsalAuthentication(interactionType, authenticationRequest, accountId
393
503
  };
394
504
  }, [instance, logger]);
395
505
  React.useEffect(() => {
396
- if (!hasBeenCalled && !error && !isAuthenticated && inProgress === msalBrowser.InteractionStatus.None) {
397
- logger.info("useMsalAuthentication - No user is authenticated, attempting to login"); // Ensure login is only called one time from within this hook, any subsequent login attempts should use the callback returned
398
-
399
- setHasBeenCalled(true);
400
- login().catch(() => {
401
- // Errors are handled by the event handler above
402
- return;
403
- });
506
+ if (shouldAcquireToken.current && inProgress === msalBrowser.InteractionStatus.None) {
507
+ shouldAcquireToken.current = false;
508
+
509
+ if (!isAuthenticated) {
510
+ logger.info("useMsalAuthentication - No user is authenticated, attempting to login");
511
+ login().catch(() => {
512
+ // Errors are saved in state above
513
+ return;
514
+ });
515
+ } else if (account) {
516
+ logger.info("useMsalAuthentication - User is authenticated, attempting to acquire token");
517
+ acquireToken().catch(() => {
518
+ // Errors are saved in state above
519
+ return;
520
+ });
521
+ }
404
522
  }
405
- }, [isAuthenticated, inProgress, error, hasBeenCalled, login, logger]);
523
+ }, [isAuthenticated, account, inProgress, login, acquireToken, logger]);
406
524
  return {
407
525
  login,
526
+ acquireToken,
408
527
  result,
409
528
  error
410
529
  };