From f0642cbf9626c4b6d9ace4303413f9f77730dca7 Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 25 Jan 2025 17:37:32 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8:=20Add=20useAutoSignin=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useAutoSignin.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/useAutoSignin.ts diff --git a/src/useAutoSignin.ts b/src/useAutoSignin.ts new file mode 100644 index 00000000..964b5806 --- /dev/null +++ b/src/useAutoSignin.ts @@ -0,0 +1,71 @@ +import React from "react"; +import { useAuth } from "./useAuth"; +import { hasAuthParams } from "./utils"; +import type { AuthContextProps } from "./AuthContext"; + +type UseAutoSignInProps = { + signinMethod?: keyof Pick; +} + +type UseAutoSignInReturn = { + isLoading: boolean; + isAuthenticated: boolean; + isError: boolean; +} + +/** + * @public + * + * Automatically attempts to sign in a user based on the provided sign-in method and authentication state. + * + * This hook manages automatic sign-in behavior for a user. It uses the specified sign-in + * method, the current authentication state, and ensures the sign-in attempt is made only once + * in the application context. + * + * Does not support the signinResourceOwnerCredentials method! + * + * @param {UseAutoSignInProps} [options='{signinMethod: "signinRedirect"}'] - Configuration object for the sign-in method. + * @param {string} [options.signinMethod="signinRedirect"] - The sign-in method to use for auto sign-in. + * Possible values are: + * - "signinRedirect": Redirects the user to the sign-in page (default). + * - "signinSilent": Signs in the user silently in the background. + * - "signinPopup": Signs in the user through a popup. + * + * @returns {UseAutoSignInReturn} - The current status of the authentication process. + * @returns {boolean} isLoading - Indicates whether the authentication process is currently in progress. + * @returns {boolean} isAuthenticated - Indicates whether the user is currently signed in. + * @returns {boolean} isError - Indicates whether there was an error during the sign-in or silent renew process. + */ + +export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignInProps = {}): UseAutoSignInReturn => { + const auth = useAuth(); + const [hasTriedSignin, setHasTriedSignin] = React.useState(false); + + React.useEffect(() => { + if (!hasAuthParams() && + !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && + !hasTriedSignin + ) { + switch (signinMethod) { + case "signinSilent": + void auth.signinSilent(); + break; + case "signinPopup": + void auth.signinPopup(); + break; + case "signinRedirect": + default: + void auth.signinRedirect(); + break; + } + + setHasTriedSignin(true); + } + }, [auth, hasTriedSignin, signinMethod]); + + return { + isLoading: auth.isLoading, + isAuthenticated: auth.isAuthenticated, + isError: !!auth.error, + }; +}; From 2f2e2efb741006f5da4bc65325405aac8faa295f Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 25 Jan 2025 17:39:29 +0100 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=85:=20Add=20useAutoSignin=20hook=20t?= =?UTF-8?q?ests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/useAutoSignin.test.tsx | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/useAutoSignin.test.tsx diff --git a/test/useAutoSignin.test.tsx b/test/useAutoSignin.test.tsx new file mode 100644 index 00000000..46d97e27 --- /dev/null +++ b/test/useAutoSignin.test.tsx @@ -0,0 +1,65 @@ +import { createWrapper } from "./helpers"; +import { renderHook, waitFor } from "@testing-library/react"; +import { UserManager } from "oidc-client-ts"; +import { useAutoSignin } from "../src/useAutoSignin"; +import type { AuthProviderProps } from "../src"; + +const settingsStub: AuthProviderProps = { + authority: "authority", + client_id: "client", + redirect_uri: "redirect", +}; + +describe("useAutoSignin", () => { + + it("should auto sign in using default signinRedirect", async () => { + const wrapper = createWrapper({ ...settingsStub }); + const { result } = renderHook(() => useAutoSignin(), { wrapper }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(UserManager.prototype.signinRedirect).toHaveBeenCalled(); + expect(UserManager.prototype.getUser).toHaveBeenCalled(); + }); + + it("should auto sign in using provided method signinRedirect", async () => { + const wrapper = createWrapper({ ...settingsStub }); + const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinRedirect" }), { wrapper }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(UserManager.prototype.signinRedirect).toHaveBeenCalled(); + expect(UserManager.prototype.getUser).toHaveBeenCalled(); + }); + + it("should auto sign in using provided method signinSilent", async () => { + const wrapper = createWrapper({ ...settingsStub }); + const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinSilent" }), { wrapper }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(UserManager.prototype.signinSilent).toHaveBeenCalled(); + expect(UserManager.prototype.getUser).toHaveBeenCalled(); + }); + + it("should auto sign in using provided method signinPopup", async () => { + const wrapper = createWrapper({ ...settingsStub }); + const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinPopup" }), { wrapper }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(UserManager.prototype.signinPopup).toHaveBeenCalled(); + expect(UserManager.prototype.getUser).toHaveBeenCalled(); + }); + + it("should auto sign and not call signinRedirect if other method provided", async () => { + const wrapper = createWrapper({ ...settingsStub }); + const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinPopup" }), { wrapper }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(UserManager.prototype.signinRedirect).not.toHaveBeenCalled(); + expect(UserManager.prototype.getUser).toHaveBeenCalled(); + }); + +}); From 70e5c53e92d799ee8abe3c502371f7bd44692348 Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 25 Jan 2025 17:44:33 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9C=A8:=20Add=20export=20of=20useAutoSig?= =?UTF-8?q?nin=20for=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/react-oidc-context.api.md | 6 ++++++ src/index.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/docs/react-oidc-context.api.md b/docs/react-oidc-context.api.md index 952b5a6d..4233d94e 100644 --- a/docs/react-oidc-context.api.md +++ b/docs/react-oidc-context.api.md @@ -98,6 +98,12 @@ export const hasAuthParams: (location?: Location) => boolean; // @public (undocumented) export const useAuth: () => AuthContextProps; +// Warning: (ae-forgotten-export) The symbol "UseAutoSignInProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "UseAutoSignInReturn" needs to be exported by the entry point index.d.ts +// +// @public +export const useAutoSignin: ({ signinMethod }?: UseAutoSignInProps) => UseAutoSignInReturn; + // @public export function withAuth

(Component: React_2.ComponentType

): React_2.ComponentType>; diff --git a/src/index.ts b/src/index.ts index 56718b02..900a5985 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from "./AuthContext"; export * from "./AuthProvider"; export type { AuthState } from "./AuthState"; export * from "./useAuth"; +export * from "./useAutoSignin"; export { hasAuthParams } from "./utils"; export * from "./withAuth"; export * from "./withAuthenticationRequired"; From b303e5ad8901553ef537f97218c9d7905bcb76cb Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 25 Jan 2025 17:54:41 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F:=20Extract=20complex=20i?= =?UTF-8?q?f=20check=20into=20named=20constant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/useAutoSignin.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/useAutoSignin.ts b/src/useAutoSignin.ts index 964b5806..ca7a0664 100644 --- a/src/useAutoSignin.ts +++ b/src/useAutoSignin.ts @@ -41,11 +41,11 @@ export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignIn const auth = useAuth(); const [hasTriedSignin, setHasTriedSignin] = React.useState(false); + const shouldAttemptSignin = React.useMemo(() => !hasAuthParams() && !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && + !hasTriedSignin, [auth.activeNavigator, auth.isAuthenticated, auth.isLoading, hasTriedSignin]); + React.useEffect(() => { - if (!hasAuthParams() && - !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && - !hasTriedSignin - ) { + if (shouldAttemptSignin) { switch (signinMethod) { case "signinSilent": void auth.signinSilent(); @@ -61,7 +61,7 @@ export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignIn setHasTriedSignin(true); } - }, [auth, hasTriedSignin, signinMethod]); + }, [auth, hasTriedSignin, shouldAttemptSignin, signinMethod]); return { isLoading: auth.isLoading, From 7b326b168e15e453e27c4460c50a1058f41d1c12 Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 25 Jan 2025 18:16:18 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=9D:=20Add=20useAutoSignin=20hook?= =?UTF-8?q?=20to=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 9dcaa40b..b4c688a5 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,37 @@ function App() { export default App; ``` +#### useAutoSignin + +Use the `useAutoSignin` hook inside the AuthProvider to automatically sign in. + +```jsx +// src/App.jsx +import React from "react"; +import { useAutoSignin } from "react-oidc-context"; + +function App() { + // If you provide no signinMethod at all, the default is signinRedirect + const { isLoading, isAuthenticated, isError } = useAutoSignin({signinMethod: "signinRedirect"}); + + if (isLoading) { + return

Signing you in/out...
; + } + + if (!isAuthenticated) { + return
Unable to log in
; + } + + if(isError) { + return
An error occured
+ } + + return
Signed in successfully
; +} + +export default App; +``` + ## Contributing We appreciate feedback and contribution to this repo! From 61d2d525244a76a691dbb65ea503876cfa912063 Mon Sep 17 00:00:00 2001 From: aBytex Date: Sat, 8 Mar 2025 20:02:00 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=91=8C:=20Remove=20signinSilent=20as?= =?UTF-8?q?=20option=20for=20the=20auto=20sign=20in=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡: Fix tsdoc based on api extractor warnings --- src/useAutoSignin.ts | 23 +++++++---------------- test/useAutoSignin.test.tsx | 10 ---------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/useAutoSignin.ts b/src/useAutoSignin.ts index ca7a0664..106d58d2 100644 --- a/src/useAutoSignin.ts +++ b/src/useAutoSignin.ts @@ -4,7 +4,7 @@ import { hasAuthParams } from "./utils"; import type { AuthContextProps } from "./AuthContext"; type UseAutoSignInProps = { - signinMethod?: keyof Pick; + signinMethod?: keyof Pick; } type UseAutoSignInReturn = { @@ -22,21 +22,15 @@ type UseAutoSignInReturn = { * method, the current authentication state, and ensures the sign-in attempt is made only once * in the application context. * - * Does not support the signinResourceOwnerCredentials method! + * Does not support the `signinResourceOwnerCredentials` method! * - * @param {UseAutoSignInProps} [options='{signinMethod: "signinRedirect"}'] - Configuration object for the sign-in method. - * @param {string} [options.signinMethod="signinRedirect"] - The sign-in method to use for auto sign-in. - * Possible values are: - * - "signinRedirect": Redirects the user to the sign-in page (default). - * - "signinSilent": Signs in the user silently in the background. - * - "signinPopup": Signs in the user through a popup. + * @param options - (Optional) Configuration object for the sign-in method. Default to `{ signinMethod: "signinRedirect" }`. + * Possible values for `signinMethod` are: + * - `"signinRedirect"`: Redirects the user to the sign-in page (default). + * - `"signinPopup"`: Signs in the user through a popup. * - * @returns {UseAutoSignInReturn} - The current status of the authentication process. - * @returns {boolean} isLoading - Indicates whether the authentication process is currently in progress. - * @returns {boolean} isAuthenticated - Indicates whether the user is currently signed in. - * @returns {boolean} isError - Indicates whether there was an error during the sign-in or silent renew process. + * @returns The current status of the authentication process. */ - export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignInProps = {}): UseAutoSignInReturn => { const auth = useAuth(); const [hasTriedSignin, setHasTriedSignin] = React.useState(false); @@ -47,9 +41,6 @@ export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignIn React.useEffect(() => { if (shouldAttemptSignin) { switch (signinMethod) { - case "signinSilent": - void auth.signinSilent(); - break; case "signinPopup": void auth.signinPopup(); break; diff --git a/test/useAutoSignin.test.tsx b/test/useAutoSignin.test.tsx index 46d97e27..9ba965e7 100644 --- a/test/useAutoSignin.test.tsx +++ b/test/useAutoSignin.test.tsx @@ -32,16 +32,6 @@ describe("useAutoSignin", () => { expect(UserManager.prototype.getUser).toHaveBeenCalled(); }); - it("should auto sign in using provided method signinSilent", async () => { - const wrapper = createWrapper({ ...settingsStub }); - const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinSilent" }), { wrapper }); - - await waitFor(() => expect(result.current).toBeDefined()); - - expect(UserManager.prototype.signinSilent).toHaveBeenCalled(); - expect(UserManager.prototype.getUser).toHaveBeenCalled(); - }); - it("should auto sign in using provided method signinPopup", async () => { const wrapper = createWrapper({ ...settingsStub }); const { result } = renderHook(() => useAutoSignin({ signinMethod: "signinPopup" }), { wrapper }); From 208f01e3c6d8bac427355985aa9663c2090f6042 Mon Sep 17 00:00:00 2001 From: aBytex Date: Tue, 11 Mar 2025 17:25:18 +0100 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=91=8C:=20Return=20error=20as=20in=20?= =?UTF-8?q?useAuth=20instead=20of=20isError=20boolean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/useAutoSignin.ts | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b4c688a5..6e1e06d3 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ import { useAutoSignin } from "react-oidc-context"; function App() { // If you provide no signinMethod at all, the default is signinRedirect - const { isLoading, isAuthenticated, isError } = useAutoSignin({signinMethod: "signinRedirect"}); + const { isLoading, isAuthenticated, error } = useAutoSignin({signinMethod: "signinRedirect"}); if (isLoading) { return
Signing you in/out...
; @@ -334,7 +334,7 @@ function App() { return
Unable to log in
; } - if(isError) { + if(error) { return
An error occured
} diff --git a/src/useAutoSignin.ts b/src/useAutoSignin.ts index 106d58d2..215124e8 100644 --- a/src/useAutoSignin.ts +++ b/src/useAutoSignin.ts @@ -2,16 +2,13 @@ import React from "react"; import { useAuth } from "./useAuth"; import { hasAuthParams } from "./utils"; import type { AuthContextProps } from "./AuthContext"; +import type { AuthState } from "./AuthState"; type UseAutoSignInProps = { signinMethod?: keyof Pick; } -type UseAutoSignInReturn = { - isLoading: boolean; - isAuthenticated: boolean; - isError: boolean; -} +type UseAutoSignInReturn = Pick /** * @public @@ -57,6 +54,6 @@ export const useAutoSignin = ({ signinMethod = "signinRedirect" }: UseAutoSignIn return { isLoading: auth.isLoading, isAuthenticated: auth.isAuthenticated, - isError: !!auth.error, + error: auth.error, }; };