AxaFrance / oidc-client

Light, Secure, Pure Javascript OIDC (Open ID Connect) Client. We provide also a REACT wrapper (compatible NextJS, etc.).
MIT License
596 stars 160 forks source link

Inject own Components to display breaks axa-fr/react-oidc? #1110

Open hsul-git opened 1 year ago

hsul-git commented 1 year ago

I have two components. One is the AF/R-O config and the other renders components dependend on the state. The Problem is, when a AF/R-O auth state occurs which causes a selfdefined component to display, but the whole page seems to not change anymore. If i click the button to navigate via "takeRedirect" the url changes but the selfdefined component stay instead of rendering the content of the url "/".

Also it seems the code doesnt get executed anymore. On "/" there is a useEffect which directly redirects to a login, which doesnt work then.

The only fix is to close the whole page and revisit. No Error gets provided in this case. Is there a fix or do i inject the components falsely into the OidcProvider?

Is it even possible to redirect when a auth state from AF/R-O is displayed?

Here is my code for further inspection:

Authentication.tsx:

import React from "react";
import css from "./cssModules/Authentication.module.css";
import { OidcProvider } from "@axa-fr/react-oidc";
import OutputAuthState from "./OutputAuthState";

const envVarAuthClient = process.env.REACT_APP_CLIENT_ID;
const envVarAuthProvider = process.env.REACT_APP_AUTHORITY;

const configuration = {
  client_id: envVarAuthClient ? envVarAuthClient : "",
  redirect_uri: window.location.origin + "/authentication/callback",
  silent_redirect_uri:
    window.location.origin + "/authentication/silent-callback", 
  scope: "openid profile email",
  refresh_time_before_tokens_expiration_in_second: -86300,
  authority: envVarAuthProvider ? envVarAuthProvider : "",
  service_worker_relative_url: "/OidcServiceWorker.js",
  service_worker_only: false,
};

const authenticatingComponent = () => (
  <OutputAuthState state="authenticating" />
);
const loadingComponent = () => <OutputAuthState state="loading" />;
const sessionLostComponent = () => <OutputAuthState state="sessionLost" />;
const authenticatingErrorComponent = () => (
  <OutputAuthState state="authenticatingError" />
);
const callbackSuccessComponent = () => (
  <OutputAuthState state="callbackSuccess" />
);
const serviceWorkerNotSupportedComponent = () => (
  <OutputAuthState state="serviceWorkerNotSupported" />
);

const Authentication = (props: any) => {
  return (
    <OidcProvider
      configuration={configuration}
      authenticatingComponent={authenticatingComponent}
      loadingComponent={loadingComponent}
      sessionLostComponent={sessionLostComponent}
      authenticatingErrorComponent={authenticatingErrorComponent}
      callbackSuccessComponent={callbackSuccessComponent}
      serviceWorkerNotSupportedComponent={serviceWorkerNotSupportedComponent}
    >
      {props.children}
    </OidcProvider>
  );
};

export default Authentication;

OutputAuthState.tsx:

import React, { ComponentType } from "react";
import css from "./cssModules/OutputAuthState.module.css";
import errorLogo from "../../Logo_Error.svg";
import logo from "../../Logo.svg";
import { useOidc } from "@axa-fr/react-oidc";
import { Navigate, useNavigate } from "react-router-dom";
import { Box } from "@mui/system";
import { Button, Typography, Link } from "@mui/material";

type OutputAuthStateProps = {
  state: string;
};

const OutputAuthState: React.FC<OutputAuthStateProps> = ({ state }) => {
  const { isAuthenticated } = useOidc();
  const navigate = useNavigate();
  let authStateTitle;
  let authStateText;

  (() => {
    switch (state) {
      case "authenticating":
        authStateTitle = "Redirecting to Authentication";
        authStateText = "You are being redirected to the authentication page";
        break;
      case "loading":
        authStateTitle = null;
        authStateText = "Please wait while loading...";
        break;
      case "sessionLost":
        authStateTitle = "Session Lost";
        authStateText = "Your session has been lost";
        break;
      case "authenticatingError":
        authStateTitle = "Authentication Error";
        authStateText = "An error occured during authentication";
        break;
      case "callbackSuccess":
        authStateTitle = "Authentication Success";
        authStateText = "You have been successfully authenticated";
        break;
      case "serviceWorkerNotSupported":
        authStateTitle = "Javascript Disabled";
        authStateText = "Please enable javascript to use this application";
        break;
      default:
        authStateTitle = "Unknown Error";
        authStateText = "An unknown error has occured";
        break;
    }
  })();

  const takeRedirect = () => {
    navigate("/");
  };

  return (
    <Box className={css.outerWrapperBox}>
      <Box className={css.innerWrapperBox}>
        {state === "loading" && (
          <Box className={css.loadingBox}>
            <Box
              component="img"
              className={css.spinnerImageBox}
              alt="Loading"
              src={logo}
            />
            <Typography className={css.textTypography}>
              {authStateText}
            </Typography>
          </Box>
        )}

        {(!["loading", "authenticating", "callbackSuccess"].includes(state)
        ) && (
          <Box>
            <Box>
              <img src={errorLogo} width="130" alt="LOGO" />
            </Box>
            <Box>
              <Typography className={css.titleTypography}>
                {authStateTitle}
              </Typography>
              <Typography className={css.textTypography}>
                {authStateText}
              </Typography>
              <Typography className={css.textTypography}>
                If the problem persists, please contact <Link className={css.mailLink} href="mailto:mailto:test@test.de">
            test@test.de
          </Link>
          .
              </Typography>
              <Button
                onClick={takeRedirect}
                className={css.redirectButton}
                size="small"
                variant="contained"
              >
                Back
              </Button>
            </Box>
          </Box>
        )}

        {(state === "authenticatingError" || state === "sessionLost") && isAuthenticated && (
          <Navigate to="/internalarea" replace={true} />
        )}
      </Box>
    </Box>
  );
};

export default OutputAuthState;

App.tsx:

import RouteList from "./Routes";
import { BrowserRouter as Router } from "react-router-dom";
import Authentication from "./components/auth/Authentication";
import css from "./App.module.css";
import { Box } from "@mui/system";

const App = () => {
  return (
    <Box className={css.outerWrapperBox}>
      <Box className={css.innerWrapperBox}>
        <Router>
          <Authentication>
            <RouteList />
          </Authentication>
        </Router>
      </Box>
    </Box>
  );
};

export default App;

Thank you for help.

Edit: I used the mui Link element and that seems to work. That should do the trick so the whole page refreshes. But is there a way to avoid refreshing the whole page?

guillaume-chervet commented 1 year ago

Hi @hsul-git ,

I think i need to see the code inside RouterList in order to understand your problem. Dobyou have a video or a full example i can clone to reproduce it?

hsul-git commented 1 year ago

Hi @guillaume-chervet ,

Sadly i cant provide the full code. But you should be able to reproduce it with your own project. Just add the OutputAuthState.tsx and inject the components like i did in the Authentication.tsx OidcProvider.

Then try to cause a error in axa-fr so it is forced to render a OidcProvider component which we injected. When you try to use useNavigate() to change the page it wouldnt work anymore, even if the url seems to change. The only fix is to use a link or href to completly revisit the page.

I was not able to test it myself with the standard components by OidcProvider because they have no method to return to the page before.

I will make you a video of the problem. OidcProvider component seems to stay on top and doesnt vanish and blocks the execution of code.

Routes.jsx:

import React from 'react'
import { Route, Routes } from 'react-router-dom'
import Home from './pages/Home'
import InternalArea from './pages/InternalArea'
import Prototypes from './pages/Prototypes'
import ErrorPage from './pages/ErrorPage'
import AuthDisplayPrototype from './pages/AuthDisplayPrototype'

const RouteList = () => {
  return (
<div>
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/internalarea" element={<InternalArea />} />
        <Route path="/prototypes" element={<Prototypes />} />
        <Route path="/authprototypes" element={<AuthDisplayPrototype />} />
        <Route path="/*" element={<ErrorPage />} />
    </Routes>
    </div>
  )
}

export default RouteList
hsul-git commented 1 year ago

msedge_gfL4bixdwx

guillaume-chervet commented 1 year ago

Hi @hsul-git , sorry for the delay. I will try to test it next tuesday (I couldnt before)

guillaume-chervet commented 1 year ago

Hi @hsul-git, I cannot reproduce it using the demo.

introduction

Are you able to reproduce it using the demo ?

guillaume-chervet commented 1 year ago

Hi @hsul-git , do you have dynamic component line describe in that issue?

1114