AxaFrance / oidc-client

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

useOidcUser briefly returning null on page navigation refresh during authenticated session [NextJs] #1353

Closed kramer99 closed 1 month ago

kramer99 commented 2 months ago

Issue and Steps to Reproduce

Create a starter NextJs app (choose "no" to App router, "yes" tosrc/):

npx create-next-app

Modify src/pages/index.tsx to:

import { OidcSecure, useOidcUser } from "@axa-fr/react-oidc";

const isBrowser = () => typeof window !== "undefined";

export default function Home() {
  const { oidcUser } = useOidcUser();
  console.log("isBrowser: " + isBrowser() + ", oidcUser: ");
  console.log(oidcUser);

  return (
    <div>
      Anyone can see this
      <OidcSecure>
        <div className="text-red-400">
          Not everybody can see this.... {oidcUser?.email}
        </div>
      </OidcSecure>
    </div>
  );
}

Modify src/pages/_app.tsx:

import "@/styles/globals.css";
import { OidcConfiguration, OidcProvider } from "@axa-fr/react-oidc";
import type { AppProps } from "next/app";

const oidcConfig: OidcConfiguration = {
  authority: <snip>,
  client_id: <snip>,
  service_worker_only: false,
  scope: "openid profile email offline_access",
  redirect_uri: "http://localhost:3000/ibor/callback",
};

export default function App({ Component, pageProps }: AppProps) {
  return (
    <OidcProvider configuration={oidcConfig}>
      <Component {...pageProps} />
    </OidcProvider>
  );
}

Add a callback route src/pages/ibor/callback/index.tsx:

import React from "react";
import { useOidc } from "@axa-fr/react-oidc";
import { useRouter } from "next/router";

const Page = () => {
  const { isAuthenticated } = useOidc();
  const router = useRouter();

  if (isAuthenticated) {
    router.push("/");
  } else router.push("/signin");

  return <div>Loading...</div>;
};

export default Page;

Versions

7.22.3 (I also tried the alpha version mentioned here: https://github.com/AxaFrance/oidc-client/issues/1090, with the new config option, but had no success there)

Screenshots

Expected

useOidcUser always returns the user object when user is authenticated (and we're not in an SSR context)

Actual

After authenticating, you'll see:

image

...and in the console:

image

I had at first assumed the reason would be that useOidcUser was getting called during Server Side Rendering... but as you can see, that's not the cause (note the isBrowser check). So local / session storage should be available at that time.

If you refresh the page, or navigate to another route, you'll see the same thing.

The reason this matters is that if you're taking, say, the name or email address of the logged in user and displaying them in a header, they will flicker and disappear before reappearing whenever the user navigates / refreshes. There are workarounds (you can cache it in some state somewhere) - but that's obviously not ideal.

Additional Details

guillaume-chervet commented 2 months ago

Hi @kramer99 thank you for your issue and feedback. I will make more test to debug and enhance it !

guillaume-chervet commented 2 months ago

version v7.22.4-alpha.1425 should work better @kramer99 :)

kramer99 commented 2 months ago

Confirmed, fix looks good on my side.

Thanks for the quick work @guillaume-chervet !

kramer99 commented 2 months ago

Sorry @guillaume-chervet - it looks like it was only a partial fix.

Behaviour is correct during page navigation and refresh, but there is still a problem when receiving the callback after authentication.

If you add isAuthenticated to the console.log in the example above, and then clear site data and re-authenticate, you will see this:

image
guillaume-chervet commented 2 months ago

Hi @kramer99,

I fail to reproduce it. Do you have way to reproduce it on the react oidc demo?

kramer99 commented 2 months ago

@guillaume-chervet

You can clone this: https://github.com/kramer99/oidc-test

...update _app.tsx with your IDP details and run. You should see the problem. I've tried it with two different IDPs (Okta and Google Cloud).

guillaume-chervet commented 2 months ago

thank you @kramer99 , i can reproduce it with your demo :)

guillaume-chervet commented 2 months ago

hi @kramer99 ,

I have added preload_user_info: true ine the configuration and it works on my side :)

kramer99 commented 2 months ago

I didn't notice a difference with or without the preload_user_info: true setting.

You cleared localStorage immediately before you tested successfully?

kramer99 commented 2 months ago

I have another piece of information, maybe it will help. right after the /token call returns with valid tokens, there is a call to the /userinfo endpoint which fails because no authorization header is supplied. Then a second /userinfo call is made right after which has the authorization header and thus succeds:

image

Here is that failed userinfo call as a CURL:

curl 'https://<removed>.okta.com/oauth2/v1/userinfo' \
  -H 'accept: application/json' \
  -H 'accept-language: en-US,en;q=0.9' \
  -H 'origin: http://localhost:3000' \
  -H 'referer: http://localhost:3000/' \
  -H 'sec-ch-ua: "Google Chrome";v="123", "Not:A-Brand";v="8", "Chromium";v="123"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "macOS"' \
  -H 'sec-fetch-dest: empty' \
  -H 'sec-fetch-mode: cors' \
  -H 'sec-fetch-site: cross-site' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'

(this is with preload_user_info: true, in the config)

attiqeurrehman commented 2 months ago

@guillaume-chervet I'm experiencing similar issue where after login, the oidcUserobject is initially null. Additionally, the subsequent call to the userinfoendpoint returns an unauthorized error. There is no additional call and have to refresh the page and then everything works fine.

Following is the configuration:

export const configurationIdentityServerWithoutServiceWorker = {
  client_id: 'interactive.public.short',
  redirect_uri: window.location.origin + '/callback',
  silent_redirect_uri: window.location.origin + '/silent-callback',
  scope: IDENTITY_SERVER_SCOPE,
  authority: 'http://localhost:8080',
  // authority_time_cache_wellknowurl_in_second: 60* 60,
  refresh_time_before_tokens_expiration_in_second: 40,
  storage: localStorage,
  //
  preload_user_info: true
};

Version: 7.22.4

guillaume-chervet commented 2 months ago

Hi @attiqeurrehman thank you for your feedback again. I will investigate it after my holiday near the 13 may.

Thank to all your details I think it will be easy to reproduce and debug.

attiqeurrehman commented 1 month ago

@guillaume-chervet any update on this?

guillaume-chervet commented 1 month ago

@attiqeurrehman @kramer99 I just push the fix.

Sorry, I had big holdidays time :)

attiqeurrehman commented 1 month ago

@guillaume-chervet no worries, hope you had a good time.

kramer99 commented 1 month ago

@guillaume-chervet fix looks good on my side. I'll close this unless @attiqeurrehman objects.

attiqeurrehman commented 1 month ago

Works like a charm. You can close this. Thanks a lot!