Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/useAutoSignin hook #1476

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div>Signing you in/out...</div>;
}

if (!isAuthenticated) {
return <div>Unable to log in</div>;
}

if(isError) {
return <div>An error occured</div>
}

return <div>Signed in successfully</div>;
}

export default App;
```

## Contributing

We appreciate feedback and contribution to this repo!
Expand Down
6 changes: 6 additions & 0 deletions docs/react-oidc-context.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<P>(Component: React_2.ComponentType<P>): React_2.ComponentType<Omit<P, keyof AuthContextProps>>;

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
71 changes: 71 additions & 0 deletions src/useAutoSignin.ts
Original file line number Diff line number Diff line change
@@ -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<AuthContextProps, "signinSilent" | "signinRedirect" | "signinPopup">;
}

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);

const shouldAttemptSignin = React.useMemo(() => !hasAuthParams() && !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading &&
!hasTriedSignin, [auth.activeNavigator, auth.isAuthenticated, auth.isLoading, hasTriedSignin]);

React.useEffect(() => {
if (shouldAttemptSignin) {
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, shouldAttemptSignin, signinMethod]);

return {
isLoading: auth.isLoading,
isAuthenticated: auth.isAuthenticated,
isError: !!auth.error,
};
};
65 changes: 65 additions & 0 deletions test/useAutoSignin.test.tsx
Original file line number Diff line number Diff line change
@@ -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();
});

});