@azure/msal-react 2.0.1 → 2.0.3

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.
Files changed (47) hide show
  1. package/README.md +10 -0
  2. package/dist/MsalContext.js +1 -1
  3. package/dist/MsalContext.js.map +1 -1
  4. package/dist/MsalProvider.js +8 -2
  5. package/dist/MsalProvider.js.map +1 -1
  6. package/dist/components/AuthenticatedTemplate.js +1 -1
  7. package/dist/components/AuthenticatedTemplate.js.map +1 -1
  8. package/dist/components/MsalAuthenticationTemplate.js +1 -1
  9. package/dist/components/MsalAuthenticationTemplate.js.map +1 -1
  10. package/dist/components/UnauthenticatedTemplate.js +1 -1
  11. package/dist/components/UnauthenticatedTemplate.js.map +1 -1
  12. package/dist/components/withMsal.js +1 -1
  13. package/dist/components/withMsal.js.map +1 -1
  14. package/dist/error/ReactAuthError.js +1 -1
  15. package/dist/error/ReactAuthError.js.map +1 -1
  16. package/dist/hooks/useAccount.js +1 -1
  17. package/dist/hooks/useAccount.js.map +1 -1
  18. package/dist/hooks/useIsAuthenticated.js +1 -1
  19. package/dist/hooks/useIsAuthenticated.js.map +1 -1
  20. package/dist/hooks/useMsal.js +1 -1
  21. package/dist/hooks/useMsal.js.map +1 -1
  22. package/dist/hooks/useMsalAuthentication.js +1 -1
  23. package/dist/hooks/useMsalAuthentication.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -1
  26. package/dist/packageMetadata.d.ts +1 -1
  27. package/dist/packageMetadata.js +2 -2
  28. package/dist/packageMetadata.js.map +1 -1
  29. package/dist/utils/utilities.d.ts +2 -2
  30. package/dist/utils/utilities.js +1 -1
  31. package/dist/utils/utilities.js.map +1 -1
  32. package/package.json +12 -7
  33. package/src/MsalContext.ts +35 -0
  34. package/src/MsalProvider.tsx +210 -0
  35. package/src/components/AuthenticatedTemplate.tsx +43 -0
  36. package/src/components/MsalAuthenticationTemplate.tsx +86 -0
  37. package/src/components/UnauthenticatedTemplate.tsx +48 -0
  38. package/src/components/withMsal.tsx +34 -0
  39. package/src/error/ReactAuthError.ts +40 -0
  40. package/src/hooks/useAccount.ts +68 -0
  41. package/src/hooks/useIsAuthenticated.ts +47 -0
  42. package/src/hooks/useMsal.ts +12 -0
  43. package/src/hooks/useMsalAuthentication.ts +283 -0
  44. package/src/index.ts +35 -0
  45. package/src/packageMetadata.ts +3 -0
  46. package/src/types/AccountIdentifiers.ts +10 -0
  47. package/src/utils/utilities.ts +102 -0
@@ -0,0 +1,283 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { useCallback, useEffect, useState, useRef } from "react";
7
+ import {
8
+ PopupRequest,
9
+ RedirectRequest,
10
+ SsoSilentRequest,
11
+ InteractionType,
12
+ AuthenticationResult,
13
+ AuthError,
14
+ EventMessage,
15
+ EventType,
16
+ InteractionStatus,
17
+ SilentRequest,
18
+ InteractionRequiredAuthError,
19
+ OIDC_DEFAULT_SCOPES,
20
+ } from "@azure/msal-browser";
21
+ import { useIsAuthenticated } from "./useIsAuthenticated";
22
+ import { AccountIdentifiers } from "../types/AccountIdentifiers";
23
+ import { useMsal } from "./useMsal";
24
+ import { useAccount } from "./useAccount";
25
+ import { ReactAuthError } from "../error/ReactAuthError";
26
+
27
+ export type MsalAuthenticationResult = {
28
+ login: (
29
+ callbackInteractionType?: InteractionType | undefined,
30
+ callbackRequest?: PopupRequest | RedirectRequest | SilentRequest
31
+ ) => Promise<AuthenticationResult | null>;
32
+ acquireToken: (
33
+ callbackInteractionType?: InteractionType | undefined,
34
+ callbackRequest?: SilentRequest | undefined
35
+ ) => Promise<AuthenticationResult | null>;
36
+ result: AuthenticationResult | null;
37
+ error: AuthError | null;
38
+ };
39
+
40
+ /**
41
+ * If a user is not currently signed in this hook invokes a login. Failed logins can be retried using the login callback returned.
42
+ * If a user is currently signed in this hook attempts to acquire a token. Subsequent token requests can use the acquireToken callback returned.
43
+ * Optionally provide a request object to be used in the login/acquireToken call.
44
+ * Optionally provide a specific user that should be logged in.
45
+ * @param interactionType
46
+ * @param authenticationRequest
47
+ * @param accountIdentifiers
48
+ */
49
+ export function useMsalAuthentication(
50
+ interactionType: InteractionType,
51
+ authenticationRequest?: PopupRequest | RedirectRequest | SsoSilentRequest,
52
+ accountIdentifiers?: AccountIdentifiers
53
+ ): MsalAuthenticationResult {
54
+ const { instance, inProgress, logger } = useMsal();
55
+ const isAuthenticated = useIsAuthenticated(accountIdentifiers);
56
+ const account = useAccount(accountIdentifiers);
57
+ const [[result, error], setResponse] = useState<
58
+ [AuthenticationResult | null, AuthError | null]
59
+ >([null, null]);
60
+
61
+ // Used to prevent state updates after unmount
62
+ const mounted = useRef(true);
63
+ useEffect(() => {
64
+ return () => {
65
+ mounted.current = false;
66
+ };
67
+ }, []);
68
+
69
+ // 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
70
+ const interactionInProgress = useRef(inProgress !== InteractionStatus.None);
71
+ useEffect(() => {
72
+ interactionInProgress.current = inProgress !== InteractionStatus.None;
73
+ }, [inProgress]);
74
+
75
+ // Flag used to control when the hook calls login/acquireToken
76
+ const shouldAcquireToken = useRef(true);
77
+ useEffect(() => {
78
+ if (!!error) {
79
+ // Errors should be handled by consuming component
80
+ shouldAcquireToken.current = false;
81
+ return;
82
+ }
83
+
84
+ if (!!result) {
85
+ // Token has already been acquired, consuming component/application is responsible for renewing
86
+ shouldAcquireToken.current = false;
87
+ return;
88
+ }
89
+ }, [error, result]);
90
+
91
+ const login = useCallback(
92
+ async (
93
+ callbackInteractionType?: InteractionType,
94
+ callbackRequest?: PopupRequest | RedirectRequest | SsoSilentRequest
95
+ ): Promise<AuthenticationResult | null> => {
96
+ const loginType = callbackInteractionType || interactionType;
97
+ const loginRequest = callbackRequest || authenticationRequest;
98
+ switch (loginType) {
99
+ case InteractionType.Popup:
100
+ logger.verbose(
101
+ "useMsalAuthentication - Calling loginPopup"
102
+ );
103
+ return instance.loginPopup(loginRequest as PopupRequest);
104
+ case InteractionType.Redirect:
105
+ // This promise is not expected to resolve due to full frame redirect
106
+ logger.verbose(
107
+ "useMsalAuthentication - Calling loginRedirect"
108
+ );
109
+ return instance
110
+ .loginRedirect(loginRequest as RedirectRequest)
111
+ .then(null);
112
+ case InteractionType.Silent:
113
+ logger.verbose("useMsalAuthentication - Calling ssoSilent");
114
+ return instance.ssoSilent(loginRequest as SsoSilentRequest);
115
+ default:
116
+ throw ReactAuthError.createInvalidInteractionTypeError();
117
+ }
118
+ },
119
+ [instance, interactionType, authenticationRequest, logger]
120
+ );
121
+
122
+ const acquireToken = useCallback(
123
+ async (
124
+ callbackInteractionType?: InteractionType,
125
+ callbackRequest?: SilentRequest
126
+ ): Promise<AuthenticationResult | null> => {
127
+ const fallbackInteractionType =
128
+ callbackInteractionType || interactionType;
129
+
130
+ let tokenRequest: SilentRequest;
131
+
132
+ if (callbackRequest) {
133
+ logger.trace(
134
+ "useMsalAuthentication - acquireToken - Using request provided in the callback"
135
+ );
136
+ tokenRequest = {
137
+ ...callbackRequest,
138
+ };
139
+ } else if (authenticationRequest) {
140
+ logger.trace(
141
+ "useMsalAuthentication - acquireToken - Using request provided in the hook"
142
+ );
143
+ tokenRequest = {
144
+ ...authenticationRequest,
145
+ scopes: authenticationRequest.scopes || OIDC_DEFAULT_SCOPES,
146
+ };
147
+ } else {
148
+ logger.trace(
149
+ "useMsalAuthentication - acquireToken - No request object provided, using default request."
150
+ );
151
+ tokenRequest = {
152
+ scopes: OIDC_DEFAULT_SCOPES,
153
+ };
154
+ }
155
+
156
+ if (!tokenRequest.account && account) {
157
+ logger.trace(
158
+ "useMsalAuthentication - acquireToken - Attaching account to request"
159
+ );
160
+ tokenRequest.account = account;
161
+ }
162
+
163
+ const getToken = async (): Promise<AuthenticationResult | null> => {
164
+ logger.verbose(
165
+ "useMsalAuthentication - Calling acquireTokenSilent"
166
+ );
167
+ return instance
168
+ .acquireTokenSilent(tokenRequest)
169
+ .catch(async (e: AuthError) => {
170
+ if (e instanceof InteractionRequiredAuthError) {
171
+ if (!interactionInProgress.current) {
172
+ logger.error(
173
+ "useMsalAuthentication - Interaction required, falling back to interaction"
174
+ );
175
+ return login(
176
+ fallbackInteractionType,
177
+ tokenRequest
178
+ );
179
+ } else {
180
+ logger.error(
181
+ "useMsalAuthentication - Interaction required but is already in progress. Please try again, if needed, after interaction completes."
182
+ );
183
+ throw ReactAuthError.createUnableToFallbackToInteractionError();
184
+ }
185
+ }
186
+
187
+ throw e;
188
+ });
189
+ };
190
+
191
+ return getToken()
192
+ .then((response: AuthenticationResult | null) => {
193
+ if (mounted.current) {
194
+ setResponse([response, null]);
195
+ }
196
+ return response;
197
+ })
198
+ .catch((e: AuthError) => {
199
+ if (mounted.current) {
200
+ setResponse([null, e]);
201
+ }
202
+ throw e;
203
+ });
204
+ },
205
+ [
206
+ instance,
207
+ interactionType,
208
+ authenticationRequest,
209
+ logger,
210
+ account,
211
+ login,
212
+ ]
213
+ );
214
+
215
+ useEffect(() => {
216
+ const callbackId = instance.addEventCallback(
217
+ (message: EventMessage) => {
218
+ switch (message.eventType) {
219
+ case EventType.LOGIN_SUCCESS:
220
+ case EventType.SSO_SILENT_SUCCESS:
221
+ if (message.payload) {
222
+ setResponse([
223
+ message.payload as AuthenticationResult,
224
+ null,
225
+ ]);
226
+ }
227
+ break;
228
+ case EventType.LOGIN_FAILURE:
229
+ case EventType.SSO_SILENT_FAILURE:
230
+ if (message.error) {
231
+ setResponse([null, message.error as AuthError]);
232
+ }
233
+ break;
234
+ }
235
+ }
236
+ );
237
+ logger.verbose(
238
+ `useMsalAuthentication - Registered event callback with id: ${callbackId}`
239
+ );
240
+
241
+ return () => {
242
+ if (callbackId) {
243
+ logger.verbose(
244
+ `useMsalAuthentication - Removing event callback ${callbackId}`
245
+ );
246
+ instance.removeEventCallback(callbackId);
247
+ }
248
+ };
249
+ }, [instance, logger]);
250
+
251
+ useEffect(() => {
252
+ if (
253
+ shouldAcquireToken.current &&
254
+ inProgress === InteractionStatus.None
255
+ ) {
256
+ shouldAcquireToken.current = false;
257
+ if (!isAuthenticated) {
258
+ logger.info(
259
+ "useMsalAuthentication - No user is authenticated, attempting to login"
260
+ );
261
+ login().catch(() => {
262
+ // Errors are saved in state above
263
+ return;
264
+ });
265
+ } else if (account) {
266
+ logger.info(
267
+ "useMsalAuthentication - User is authenticated, attempting to acquire token"
268
+ );
269
+ acquireToken().catch(() => {
270
+ // Errors are saved in state above
271
+ return;
272
+ });
273
+ }
274
+ }
275
+ }, [isAuthenticated, account, inProgress, login, acquireToken, logger]);
276
+
277
+ return {
278
+ login,
279
+ acquireToken,
280
+ result,
281
+ error,
282
+ };
283
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /**
7
+ * @packageDocumentation
8
+ * @module @azure/msal-react
9
+ */
10
+
11
+ export type { IMsalContext } from "./MsalContext";
12
+ export type { MsalProviderProps } from "./MsalProvider";
13
+ export type { MsalAuthenticationResult } from "./hooks/useMsalAuthentication";
14
+ export type { MsalAuthenticationProps } from "./components/MsalAuthenticationTemplate";
15
+ export type { AuthenticatedTemplateProps } from "./components/AuthenticatedTemplate";
16
+ export type { UnauthenticatedTemplateProps } from "./components/UnauthenticatedTemplate";
17
+ export type { WithMsalProps } from "./components/withMsal";
18
+ export type { AccountIdentifiers } from "./types/AccountIdentifiers";
19
+
20
+ export { MsalContext, MsalConsumer } from "./MsalContext";
21
+ export { MsalProvider } from "./MsalProvider";
22
+
23
+ export { AuthenticatedTemplate } from "./components/AuthenticatedTemplate";
24
+ export { UnauthenticatedTemplate } from "./components/UnauthenticatedTemplate";
25
+ export { MsalAuthenticationTemplate } from "./components/MsalAuthenticationTemplate";
26
+
27
+ export { withMsal } from "./components/withMsal";
28
+ export { Subtract, SetComplement, SetDifference } from "./utils/utilities";
29
+
30
+ export { useMsal } from "./hooks/useMsal";
31
+ export { useAccount } from "./hooks/useAccount";
32
+ export { useIsAuthenticated } from "./hooks/useIsAuthenticated";
33
+ export { useMsalAuthentication } from "./hooks/useMsalAuthentication";
34
+
35
+ export { version } from "./packageMetadata";
@@ -0,0 +1,3 @@
1
+ /* eslint-disable header/header */
2
+ export const name = "@azure/msal-react";
3
+ export const version = "2.0.3";
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { AccountInfo } from "@azure/msal-browser";
7
+
8
+ export type AccountIdentifiers = Partial<
9
+ Pick<AccountInfo, "homeAccountId" | "localAccountId" | "username">
10
+ >;
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { AccountIdentifiers } from "../types/AccountIdentifiers";
7
+ import { AccountInfo } from "@azure/msal-browser";
8
+
9
+ type FaaCFunction = <T>(args: T) => React.ReactNode;
10
+
11
+ export function getChildrenOrFunction<T>(
12
+ children: React.ReactNode | FaaCFunction,
13
+ args: T
14
+ ): React.ReactNode {
15
+ if (typeof children === "function") {
16
+ return children(args);
17
+ }
18
+ return children;
19
+ }
20
+
21
+ /*
22
+ * Utility types
23
+ * Reference: https://github.com/piotrwitek/utility-types
24
+ */
25
+ export type SetDifference<A, B> = A extends B ? never : A;
26
+ export type SetComplement<A, A1 extends A> = SetDifference<A, A1>;
27
+ export type Subtract<T extends T1, T1 extends object> = Pick<
28
+ T,
29
+ SetComplement<keyof T, keyof T1>
30
+ >;
31
+
32
+ /**
33
+ * Helper function to determine whether 2 arrays are equal
34
+ * Used to avoid unnecessary state updates
35
+ * @param arrayA
36
+ * @param arrayB
37
+ */
38
+ export function accountArraysAreEqual(
39
+ arrayA: Array<AccountIdentifiers>,
40
+ arrayB: Array<AccountIdentifiers>
41
+ ): boolean {
42
+ if (arrayA.length !== arrayB.length) {
43
+ return false;
44
+ }
45
+
46
+ const comparisonArray = [...arrayB];
47
+
48
+ return arrayA.every((elementA) => {
49
+ const elementB = comparisonArray.shift();
50
+ if (!elementA || !elementB) {
51
+ return false;
52
+ }
53
+
54
+ return (
55
+ elementA.homeAccountId === elementB.homeAccountId &&
56
+ elementA.localAccountId === elementB.localAccountId &&
57
+ elementA.username === elementB.username
58
+ );
59
+ });
60
+ }
61
+
62
+ export function getAccountByIdentifiers(
63
+ allAccounts: AccountInfo[],
64
+ accountIdentifiers: AccountIdentifiers
65
+ ): AccountInfo | null {
66
+ if (
67
+ allAccounts.length > 0 &&
68
+ (accountIdentifiers.homeAccountId ||
69
+ accountIdentifiers.localAccountId ||
70
+ accountIdentifiers.username)
71
+ ) {
72
+ const matchedAccounts = allAccounts.filter((accountObj) => {
73
+ if (
74
+ accountIdentifiers.username &&
75
+ accountIdentifiers.username.toLowerCase() !==
76
+ accountObj.username.toLowerCase()
77
+ ) {
78
+ return false;
79
+ }
80
+ if (
81
+ accountIdentifiers.homeAccountId &&
82
+ accountIdentifiers.homeAccountId.toLowerCase() !==
83
+ accountObj.homeAccountId.toLowerCase()
84
+ ) {
85
+ return false;
86
+ }
87
+ if (
88
+ accountIdentifiers.localAccountId &&
89
+ accountIdentifiers.localAccountId.toLowerCase() !==
90
+ accountObj.localAccountId.toLowerCase()
91
+ ) {
92
+ return false;
93
+ }
94
+
95
+ return true;
96
+ });
97
+
98
+ return matchedAccounts[0] || null;
99
+ } else {
100
+ return null;
101
+ }
102
+ }