1

oidc initializes and signInRedirectCallback is successful, also sessionStorage is set properly., how ever this signInRedirectCallback is being called continuously ( please see screen shot ). The html page shows "Callback Loading..." from the callback component. Not going back to homepage after sessionStorage is set.

Not sure what am I doing wrong.

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

App.js

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import {PrivateRoute} from './PrivateRoute';
import HomePage from './HomePage';
import {Callback} from "./Callback";
import {Logout} from "./Logout";
import {SilentRenew} from "./SilentRenew";
import {AuthProvider} from "./AuthProvider";
import Profile from "./Profile";

function App() {

  return (
      <Router>
          <AuthProvider>
              <Routes>
                  <Route exact path="/signin-oidc" element={<Callback />} />
                  <Route exact path="/" element={<PrivateRoute > <HomePage/> </PrivateRoute>}  /> } />
                  <Route exact path="/silentrenew" element={<SilentRenew /> } />
                  <Route exact path="/profile" element={<PrivateRoute > <Profile/> </PrivateRoute>}  /> } />
                  <Route exact path="/logout" element={<Logout />} />
              </Routes>
          </AuthProvider>
      </Router>
  );
}
export default App;

HomePage.js

import {Link} from "react-router-dom";
export default function HomePage()
{
    return (
        <div>
            home page
            <br></br><br></br>
            <Link to="profile" >Profile Page</Link>

        </div>
    )
}

Callback.js

import * as React from 'react';

import {AuthConsumer} from "./AuthProvider";

export const Callback = () => (
    <AuthConsumer>
        {({ signInRedirectCallback }) => {
            signInRedirectCallback();
            return <span>Callback loading</span>;
        }}
    </AuthConsumer>
);

PrivateRoute.js

import * as React from 'react';

import {AuthConsumer} from "./AuthProvider";

export const PrivateRoute = (children) => (
    <AuthConsumer>
       {({ isAuthenticated, signInRedirect }) => {
           console.log(isAuthenticated)
           console.log(sessionStorage.isAuthenticated)
           
           if(isAuthenticated) {
             return children
           }
           else if(sessionStorage.isAuthenticated === 'true') {
             return children
           }
           else {
               signInRedirect();
               return <span>PrivateRoute loading</span>;
           }
       }}
    </AuthConsumer>
);

AuthProvider.js

import React, { createContext, useContext, useEffect, useState } from "react";
import { UserManager } from "oidc-client";
export const oidcConfig = {
    authority: 'https://idp.myid-helloworld.com/.well-known/openid-configuration',
    client_id: 'client-20230306-0001',
    redirect_uri: 'http://localhost:3000/signin-oidc',
    response_type: 'id_token token',
    automaticSilentRenew: true,
    loadUserInfo: true,
    scope: 'openid profile email',
};



export const userManager = new UserManager({...oidcConfig});

const AuthContext = createContext(undefined)

export const AuthConsumer = AuthContext.Consumer;


export function AuthProvider({ children }) {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [user, setUser] = useState(null);

    useEffect(() => {

        const loadUserFromStorage = async () => {

            let user = await userManager.getUser();
            if (user) {
                console.log("User flow")
                setIsAuthenticated(true);
                setUser(user);
                sessionStorage.setItem('access_token', user.access_token);
                sessionStorage.setItem('id_token', user.id_token);
                sessionStorage.setItem('isAuthenticated', isAuthenticated)
            }
        };

        loadUserFromStorage().then(r => '');
    }, [isAuthenticated, user]);

    const login = async () => {
        console.log("in signin-redirect")
        await userManager.signinRedirect();
    };

    const logout = async () => {
        await userManager.signoutRedirect();
    };

   const signInRedirectCallback = async () => {
       console.log('signInRedirectCallback flow!');
       let user = await userManager.getUser();
       if(!user) {
           await userManager.signinRedirectCallback()
               .then(() => {
                   console.log('signInRedirectCallback Successful!');
               })
               .catch((error) => {
                   console.error('signInRedirectCallback Error : ', error);
               });

           setIsAuthenticated(true);
           setUser(user);
           sessionStorage.setItem('access_token', user.access_token);
           sessionStorage.setItem('id_token', user.id_token);
           sessionStorage.setItem('isAuthenticated', isAuthenticated)
       }
   };

   const signInRedirect = () => {
       sessionStorage.setItem('redirectUri', window.location.pathname);
       userManager.signinRedirect({}).then(() => { console.log('Private Route Successful!');})
           .catch((error) => { console.error('Private Route Error : ', error);});
    };

    return (
        <AuthContext.Provider value={{ signInRedirect, signInRedirectCallback, isAuthenticated, user, login, logout }}>
            {children}
        </AuthContext.Provider>
    );
}

export function useAuth() {
    return useContext(AuthContext);
}


console log - screen shot

enter image description here


Prakash Raj
  • 149
  • 3
  • 13

1 Answers1

1

The signInRedirectCallback function is called as an unintentional side-effect any time the Callback component is rendered. The function should be called as an intentional side-effect either in a useEffect hook in a React function component or from the componentDidMount or componentDidUpdate React class-based component lifecycle methods.

The code is using old React context consumer syntax which makes it a bit more tedious to abstract calling signInRedirectCallback in one of these places. It would be easier, and clearer, to use the useContext hook instead.

Example:

Callback.js

import * as React from 'react';
import { useAuth } from "./AuthProvider";

export const Callback = () => {
  const { signInRedirectCallback } = useAuth();

  React.useEffect(() => {
    signInRedirectCallback();
  }, [signInRedirectCallback]);

  return <span>Callback loading</span>
};

PrivateRoute.js

import * as React from 'react';
import { useAuth } from "./AuthProvider";

export const PrivateRoute = ({ children }) => {
  const { isAuthenticated, signInRedirect } = useAuth();

  React.useEffect(() => {
    if (!isAuthenticated) {
      signInRedirect();
    }
  }, [signInRedirect, isAuthenticated]);

  return isAuthenticated
    ? children
    : <span>PrivateRoute loading</span>;
);
export function AuthProvider({ children }) {
  const [isAuthenticated, setIsAuthenticated] = useState();
  const [user, setUser] = useState();

  useEffect(() => {
    const loadUserFromStorage = async () => {
      const user = await userManager.getUser();
      setIsAuthenticated(!!user);
      setUser(user);
    };

    loadUserFromStorage();
  }, []);

  // Persist state changes to sessionStorage
  useEffect(() => {
    sessionStorage.setItem('access_token', JSON.stringify(user?.access_token));
    sessionStorage.setItem('id_token', JSON.stringify(user?.id_token));
    sessionStorage.setItem('isAuthenticated', JSON.stringify(isAuthenticated));
  }, [isAuthenticated, user]);

  ...

  const value = {
    signInRedirect,
    signInRedirectCallback,
    isAuthenticated,
    user,
    login,
    logout
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

It's a bit unclear where exactly any authentication redirects occur, but for a pretty standard implementation/pattern see my answer here for protected route implementation and authentication flow which should help with any auth checks/redirects for any previously authenticated users when the app is mounted or the page is reloaded.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Hi @DrewReese, I tried as you suggested. Now the continuous SignInRedirectCallback stopped, it is called only once. However it stays in callback page "Callback loading" and not navigating to "/" home page. – Prakash Raj Mar 07 '23 at 14:50
  • resolved this issue page staying in "Callback loading". In Provider.js added navigate("/") in signInRedirectCallback. Now working as expected. – Prakash Raj Mar 07 '23 at 16:22
  • @PrakashRaj Right, it wasn't clear to me where/how a user was redirected back after authenticating, but that also wasn't the main issue described in the post so I didn't press the issue and left a link to another of my answers the details an auth flow. Sounds like you got it all sorted out though. Cheers. – Drew Reese Mar 07 '23 at 16:50