@conduit-d365/auth 0.2.2 → 0.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.
@@ -18,6 +18,10 @@ interface AuthProviderProps {
18
18
  * Handles MSAL initialisation, redirect callbacks, and active account
19
19
  * management. Children are NOT rendered until MSAL has initialised and
20
20
  * any pending redirect promise has been processed.
21
+ *
22
+ * The MSAL instance is created once (via useRef) and initialised in a
23
+ * single useEffect. MsalProvider receives the instance but does NOT
24
+ * re-initialise it — MSAL internally tracks its own init state.
21
25
  */
22
26
  export declare function AuthProvider({ clientId, redirectUri, scopes, children, }: AuthProviderProps): import("react/jsx-runtime").JSX.Element | null;
23
27
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAY1E,qEAAqE;AACrE,eAAO,MAAM,aAAa,qCAAiD,CAAC;AAE5E,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,wBAAgC,CAAC;AAE9D,UAAU,iBAAiB;IACzB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,GACT,EAAE,iBAAiB,kDA6DnB"}
1
+ {"version":3,"file":"AuthProvider.d.ts","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAY1E,qEAAqE;AACrE,eAAO,MAAM,aAAa,qCAAiD,CAAC;AAE5E,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,wBAAgC,CAAC;AAE9D,UAAU,iBAAiB;IACzB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,GACT,EAAE,iBAAiB,kDAoEnB"}
@@ -13,27 +13,38 @@ export const MsalReadyContext = createContext(false);
13
13
  * Handles MSAL initialisation, redirect callbacks, and active account
14
14
  * management. Children are NOT rendered until MSAL has initialised and
15
15
  * any pending redirect promise has been processed.
16
+ *
17
+ * The MSAL instance is created once (via useRef) and initialised in a
18
+ * single useEffect. MsalProvider receives the instance but does NOT
19
+ * re-initialise it — MSAL internally tracks its own init state.
16
20
  */
17
21
  export function AuthProvider({ clientId, redirectUri, scopes, children, }) {
18
22
  const msalInstanceRef = useRef(null);
19
23
  const [msalReady, setMsalReady] = useState(false);
24
+ const initStarted = useRef(false);
20
25
  if (!msalInstanceRef.current) {
21
26
  const config = createMsalConfig({ clientId, redirectUri });
22
27
  msalInstanceRef.current = new PublicClientApplication(config);
23
28
  }
24
29
  const msalInstance = msalInstanceRef.current;
25
30
  useEffect(() => {
31
+ // Guard against double-invocation in React StrictMode
32
+ if (initStarted.current)
33
+ return;
34
+ initStarted.current = true;
26
35
  const init = async () => {
27
36
  await msalInstance.initialize();
28
- // Process any pending redirect response BEFORE rendering children
29
- const response = await msalInstance.handleRedirectPromise();
37
+ // Process any pending redirect response BEFORE rendering children.
38
+ // If this returns null after a redirect, MSAL couldn't match the
39
+ // response to a stored request (the root cause of the redirect loop).
40
+ const response = await msalInstance.handleRedirectPromise({
41
+ navigateToLoginRequestUrl: false,
42
+ });
30
43
  if (response?.account) {
31
- // Returning from a login redirect — set the account immediately
32
44
  msalInstance.setActiveAccount(response.account);
33
45
  }
34
46
  else if (!msalInstance.getActiveAccount() &&
35
47
  msalInstance.getAllAccounts().length > 0) {
36
- // Existing session — set first account as active
37
48
  msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
38
49
  }
39
50
  // Listen for future login successes
@@ -1 +1 @@
1
- {"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1E,OAAO,EACL,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,SAAS,EACT,uBAAuB,GAGxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,qEAAqE;AACrE,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAuB,SAAS,CAAC,CAAC;AAE5E,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAU,KAAK,CAAC,CAAC;AAY9D;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,QAAQ,EACR,WAAW,EACX,MAAM,EACN,QAAQ,GACU;IAClB,MAAM,eAAe,GAAG,MAAM,CAAiC,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3D,eAAe,CAAC,OAAO,GAAG,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC;IAE7C,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;YACtB,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC;YAEhC,kEAAkE;YAClE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,qBAAqB,EAAE,CAAC;YAE5D,IAAI,QAAQ,EAAE,OAAO,EAAE,CAAC;gBACtB,gEAAgE;gBAChE,YAAY,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClD,CAAC;iBAAM,IACL,CAAC,YAAY,CAAC,gBAAgB,EAAE;gBAChC,YAAY,CAAC,cAAc,EAAE,CAAC,MAAM,GAAG,CAAC,EACxC,CAAC;gBACD,iDAAiD;gBACjD,YAAY,CAAC,gBAAgB,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,oCAAoC;YACpC,YAAY,CAAC,gBAAgB,CAAC,CAAC,KAAmB,EAAE,EAAE;gBACpD,IACE,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,aAAa;oBAC3C,KAAK,CAAC,OAAO,EACb,CAAC;oBACD,MAAM,MAAM,GAAG,KAAK,CAAC,OAA+B,CAAC;oBACrD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;wBACnB,YAAY,CAAC,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAChD,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;IACT,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,KAAC,YAAY,IAAC,QAAQ,EAAE,YAAY,YAClC,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,SAAS,YACzC,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YAClC,QAAQ,GACc,GACC,GACf,CAChB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"AuthProvider.js","sourceRoot":"","sources":["../src/AuthProvider.tsx"],"names":[],"mappings":"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"}
@@ -7,9 +7,10 @@ interface ProtectedRouteProps {
7
7
  /**
8
8
  * Route guard that requires authentication.
9
9
  *
10
- * While MSAL is initialising, shows a loading indicator. If the user
11
- * is not authenticated, triggers a login redirect (once only per mount).
12
- * Otherwise renders the children.
10
+ * While MSAL is initialising or processing a redirect, shows a loading
11
+ * indicator. If the user is not authenticated AND MSAL is completely
12
+ * idle (InteractionStatus.None), triggers a login redirect once per
13
+ * mount. Otherwise renders the children.
13
14
  *
14
15
  * Usage:
15
16
  * <Route path="/projects" element={
@@ -1 +1 @@
1
- {"version":3,"file":"ProtectedRoute.d.ts","sourceRoot":"","sources":["../src/ProtectedRoute.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiB,MAAM,OAAO,CAAC;AAGtC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,GACjB,EAAE,mBAAmB,2CAiBrB"}
1
+ {"version":3,"file":"ProtectedRoute.d.ts","sourceRoot":"","sources":["../src/ProtectedRoute.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAKjD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,GACjB,EAAE,mBAAmB,2CA6BrB"}
@@ -1,12 +1,15 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useRef } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
  import { useAuth } from "./useAuth";
4
+ import { useMsal } from "@azure/msal-react";
5
+ import { InteractionStatus } from "@azure/msal-browser";
4
6
  /**
5
7
  * Route guard that requires authentication.
6
8
  *
7
- * While MSAL is initialising, shows a loading indicator. If the user
8
- * is not authenticated, triggers a login redirect (once only per mount).
9
- * Otherwise renders the children.
9
+ * While MSAL is initialising or processing a redirect, shows a loading
10
+ * indicator. If the user is not authenticated AND MSAL is completely
11
+ * idle (InteractionStatus.None), triggers a login redirect once per
12
+ * mount. Otherwise renders the children.
10
13
  *
11
14
  * Usage:
12
15
  * <Route path="/projects" element={
@@ -17,15 +20,24 @@ import { useAuth } from "./useAuth";
17
20
  */
18
21
  export function ProtectedRoute({ children, loadingComponent, }) {
19
22
  const { isAuthenticated, isLoading, login } = useAuth();
23
+ const { inProgress } = useMsal();
20
24
  const loginTriggered = useRef(false);
21
- if (isLoading) {
22
- return _jsx(_Fragment, { children: loadingComponent ?? _jsx("div", { children: "Loading..." }) });
23
- }
24
- if (!isAuthenticated) {
25
- if (!loginTriggered.current) {
25
+ useEffect(() => {
26
+ // Only trigger login when MSAL is completely idle and user is not
27
+ // authenticated. This prevents firing loginRedirect while MSAL is
28
+ // still processing a redirect response (which caused the loop).
29
+ if (!isAuthenticated &&
30
+ !isLoading &&
31
+ inProgress === InteractionStatus.None &&
32
+ !loginTriggered.current) {
26
33
  loginTriggered.current = true;
27
34
  login();
28
35
  }
36
+ }, [isAuthenticated, isLoading, inProgress, login]);
37
+ if (isLoading || inProgress !== InteractionStatus.None) {
38
+ return _jsx(_Fragment, { children: loadingComponent ?? _jsx("div", { children: "Loading..." }) });
39
+ }
40
+ if (!isAuthenticated) {
29
41
  return _jsx("div", { children: "Redirecting to login..." });
30
42
  }
31
43
  return _jsx(_Fragment, { children: children });
@@ -1 +1 @@
1
- {"version":3,"file":"ProtectedRoute.js","sourceRoot":"","sources":["../src/ProtectedRoute.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,GACI;IACpB,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IACxD,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAErC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,4BAAG,gBAAgB,IAAI,uCAAqB,GAAI,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC5B,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;YAC9B,KAAK,EAAE,CAAC;QACV,CAAC;QACD,OAAO,oDAAkC,CAAC;IAC5C,CAAC;IAED,OAAO,4BAAG,QAAQ,GAAI,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"ProtectedRoute.js","sourceRoot":"","sources":["../src/ProtectedRoute.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAQxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,QAAQ,EACR,gBAAgB,GACI;IACpB,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC;IACxD,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAAC;IACjC,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAErC,SAAS,CAAC,GAAG,EAAE;QACb,kEAAkE;QAClE,kEAAkE;QAClE,gEAAgE;QAChE,IACE,CAAC,eAAe;YAChB,CAAC,SAAS;YACV,UAAU,KAAK,iBAAiB,CAAC,IAAI;YACrC,CAAC,cAAc,CAAC,OAAO,EACvB,CAAC;YACD,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;YAC9B,KAAK,EAAE,CAAC;QACV,CAAC;IACH,CAAC,EAAE,CAAC,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IAEpD,IAAI,SAAS,IAAI,UAAU,KAAK,iBAAiB,CAAC,IAAI,EAAE,CAAC;QACvD,OAAO,4BAAG,gBAAgB,IAAI,uCAAqB,GAAI,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,oDAAkC,CAAC;IAC5C,CAAC;IAED,OAAO,4BAAG,QAAQ,GAAI,CAAC;AACzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduit-d365/auth",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Shared Entra ID authentication React components for the Conduit suite",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",