AxaFrance / oidc-client

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

How can I get the accessToken inside the callbackSuccessComponent? #941

Closed arus307 closed 1 year ago

arus307 commented 1 year ago

Issue and Steps to Reproduce

I'm trying the demo project and wanted to set the callbackSuccessComponent and get the access token, but I can't.

App.tsx

import React, { useReducer } from 'react';
import { BrowserRouter, NavLink, Route, Routes } from 'react-router-dom';

import CallbackSuccess from './CallBackSuccess';
import { configurationIdentityServer } from './configurations';
import { FetchUserHoc, FetchUserHook } from './FetchUser';
import { Home } from './Home';
import { MultiAuthContainer } from './MultiAuth';
import { OidcProvider, withOidcSecure } from './oidc';
import { Profile, SecureProfile } from './Profile';

const OidcSecureHoc = withOidcSecure(Profile);

const getRandomInt = (max) => {
  return Math.floor(Math.random() * max);
};

function reducer(state, action) {
  switch (action.type) {
    case 'event':
      {
        const id = getRandomInt(9999999999999).toString();
        return [{ ...action.data, id, date: Date.now() }, ...state];
      }
    default:
      throw new Error();
  }
}

function App() {
  // eslint-disable-next-line @typescript-eslint/naming-convention
  const [show, setShow] = React.useState(false);
  const [events, dispatch] = useReducer(reducer, []);

  const onEvent = (configurationName, eventName, data) => {
   // console.log(`oidc:${configurationName}:${eventName}`, data);
    dispatch({ type: 'event', data: { name: `oidc:${configurationName}:${eventName}`, data } });
  };
  return (<>
    <OidcProvider configuration={configurationIdentityServer} onEvent={onEvent}

    /* Added here */
    callbackSuccessComponent={CallbackSuccess}
    >
      <BrowserRouter>
        <nav className="navbar navbar-expand-lg navbar-dark bg-primary">
          <a className="navbar-brand" href="/">@axa-fr/react-oidc</a>
          <button className="navbar-toggler" type="button" onClick={() => setShow(!show)} data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span className="navbar-toggler-icon"/>
          </button>
          <div style={show ? { display: 'block' } : { display: 'none' }} className="collapse navbar-collapse" id="navbarNav">
            <ul className="navbar-nav">
              <li className="nav-item">
                <NavLink className="nav-link" to="/">Home</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/profile">Profile</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/profile-secure-component">Secure Profile Component</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/profile-secure-hoc">Secure Profile Hoc</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/user-fetch-secure-hoc">Secure User Fetch Hoc</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/user-fetch-secure-hook">Secure User Fetch Hook</NavLink>
              </li>
              <li className="nav-item">
                <NavLink className="nav-link" to="/multi-auth">Multi Auth</NavLink>
              </li>
            </ul>
          </div>
        </nav>

        <div>
          <Routes>
            <Route path="/" element={<Home></Home>} />
            <Route path="/profile" element={<Profile></Profile>} />
            <Route path="/profile-secure-component" element={<SecureProfile></SecureProfile>} />
            <Route path="/profile-secure-hoc" element={<OidcSecureHoc></OidcSecureHoc>} />
            <Route path="/user-fetch-secure-hoc" element={<FetchUserHoc></FetchUserHoc>} />
            <Route path="/user-fetch-secure-hook" element={<FetchUserHook></FetchUserHook>} />
            <Route path="/multi-auth/*" element={<MultiAuthContainer></MultiAuthContainer>} />
          </Routes>
        </div>

      </BrowserRouter>
    </OidcProvider>
       <div className="container-fluid mt-3">
        <div className="card">
          <div className="card-body" >
            <h5 className="card-title">Default configuration Events</h5>
            <div style={{ overflowX: 'hidden', overflowY: 'scroll', maxHeight: '400px' }}>
              {events.map(e => {
                const date = new Date(e.date);
                const dateFormated = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
                return <p key={e.id}>{dateFormated} {e.name}: { JSON.stringify(e.data)}</p>;
              })}
            </div>
          </div>
        </div>
      </div></>
  );
}

export default App;

CallbackSuccess.tsx

import { useOidcAccessToken } from './oidc';

const CallbackSuccess = () => {
    console.log('callback success!');

    const accessToken = useOidcAccessToken().accessToken;
    console.log(accessToken);

    return null;
};

export default CallbackSuccess;

Expected

callback success!
{{accessToken is output here}}

Actual

callback success!
null

Additional Details

After logging in and before transitioning to a page, after getting an access token, I want to send a request with the token to a backend API I'm developing to get additional information about the user like user name in my service.

guillaume-chervet commented 1 year ago

Hi @arus307 , thank you very much for your issue. I am not sure if it is possible with the actual code : https://github.com/AxaGuilDEv/react-oidc/blob/e814384b2a8d84c895205db5d965d2b61936ec5f/packages/react/src/oidc/core/default-component/Callback.component.tsx#L21 I have to think for a very simple solution. Do you have an idea?

guillaume-chervet commented 1 year ago

Hi @arus307 ,

It is possible to add a callback like the onsession lost. onSessionLost={onSessionLost}

But it will require to call the vanilla api getOidc(configurationName).loginCallbackAsync(); From somewhere on your own. For example: onCallback={onCallback}

arus307 commented 1 year ago

Thanks for your comment @guillaume-chervet

getOidc(configurationName).loginCallbackAsync();

I used this in CallbackSuccess component and I got accessToken.

Thank you for your answer!

guillaume-chervet commented 1 year ago

Hi @arus307 , i think i should add a callback method like the onSession lost. If your are using the reactcomponentcallback override, it can bug because callback method is called 2 times.

arus307 commented 1 year ago

Thank you @guillaume-chervet .

I may not have understood it well enough.

I am using the flag with useRef in CallbackSuccess to suppress the two calls.

so it look like it is only called once.

But is it still a possible bug?

If I add a callback method like the session lost, could it be written smarter?

guillaume-chervet commented 1 year ago

@arus307 , i am curious now. I do not have any idea about how you did it. Do you have a sample of code?

arus307 commented 1 year ago

@guillaume-chervet I'm sorry, it seems I misread.

I wrote code to prevent it from being called twice inside the CallbackSuccessComponent.

like this

const CallbackSuccess = () => {

    // some code

    if(isFirstTimeInThisComponent){
        //called only once "in this component"
        getOidc(configurationName).loginCallbackAsync()
    }
    return null;
};

If your are using the reactcomponentcallback override, it can bug because callback method is called 2 times.

Did this means getOidc(configurationName).loginCallbackAsync() will be called twice with the following timing?

1.In library react-oidc/packages/react/src/oidc/core/default-component/Callback.component.tsx

  1. In my CallbackSuccessComponent