AxaFrance / oidc-client

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

State not valid error in step-up authentication #993

Open raffaeler opened 1 year ago

raffaeler commented 1 year ago

Issue and Steps to Reproduce

In a step-up scenario, the user is already logged in using username/password. If the user logout/login with the TOTP, everything works fine and the user can access the protected resource. If instead the user does not logout and login requesting the TOTP, the authentication happens but after the callback, the oidc library throws an error.

Method used to login requesting a specific acr:

// the loa value is the string configured on the Identity Provider: "pwd" or "mfa" or "hwk"
await login({
   acr_values: loa
});

Error returned by the library"

Error: state not valid
  at login.ts:113:1
  at Generator.next (<anonymous>)
  at fulfilled (initWorker.ts:152:1)

GET https://localhost:3443/OidcKeepAliveServiceWorker.json net::ERR_FAILED
TypeError: Failed to fetch
  at keepAlive (initWorker.ts:112:1)
  at Object.startKeepAliveServiceWorker (initWorker.ts:227:1)
  at login.ts:91:1
  at Generator.next (<anonymous>)
  at fulfilled (initWorker.ts:152:1)

Versions

6.15.8

Additional Details

I suspect the library gets confused when the user is already logged in but the new token (the one with the higher acr value) is returned from the identity provider.

Thank you

guillaume-chervet commented 1 year ago

Hi @raffaeler , thank you very much for your issue. It look like a bug like previous state is keeped instead of new state.

raffaeler commented 1 year ago

Hi @guillaume-chervet are you planning a fix or is there a workaround I can momentarily use? Thanks!

guillaume-chervet commented 1 year ago

Hi @raffaeler , of course we will fix this. I am pretty busy these weeks. I will try to fix it next week. I dont think there is a workaround :(

guillaume-chervet commented 1 year ago

Hi @raffaeler , do you have a sample of your configuration ?

guillaume-chervet commented 1 year ago

Hi @raffaeler ,

Did you try to set the extras parameter to the second place? Here the definition : const login = (callbackPath:string | undefined = undefined, extras:StringMap = null, silentLoginOnly = false)

raffaeler commented 1 year ago

Thanks for your answer @guillaume-chervet I confirm that login pass the extras as the second parameter. More specifically the code does the following steps:

Some comment about the code:

The final error is: image


const acr_to_loa = Object.freeze({
  pwd: 1,
  mfa: 2,
  hwk: 3,
});

  const invokeAPI = async (resource, requested_loa, previousInvocationOk = true) => {
    try {
      console.log(`requesting ${resource} with loa:${requested_loa}`)
      const token = accessToken;
      if (!isAuthenticated) {
        setResult("User is not authenticated");
        setIsError(true);
        return;
      }

      let token_loa = acr_to_loa[accessTokenPayload.acr];

      let origin = window.location.origin;
      const response = await fetch(origin + "/api/values/" + resource, {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (!response.ok) {
        let message;
        try {
          console.log(response);
          var authError = response.headers.get("WWW-Authenticate");
          message = `Fetch failed with HTTP status ${response.status} ${authError}  ${await response.text()}`;
        }
        catch (e) {
          message = `Fetch failed with HTTP status ${response.status} ${response.statusText}`;
        }

        if(response.status == 403) {
          console.log("fetch access denied: logout+login in progress: " + requested_loa);
          //await logout();
          await login(null, {
            acr_values: requested_loa
          });

          invokeAPI(resource, requested_loa, false);
          return;
        }

        setResult(message);
        setIsError(true);
        return;
      }

      setResult(await response.json());
      setIsError(false);
    }
    catch (e) {
      console.log(e);
      setResult(e.message);
      setIsError(true);
    }
  }

Thank you

raffaeler commented 1 year ago

Configuration as requested:

// The IP is Keycloak
const configuration = {
  authority: "https://XXXXXXXXXX/realms/YYYYYYY",
  client_id: "ZZZZZZZZ",
  redirect_uri: window.location.origin + '/authentication/callback',
  silent_redirect_uri: window.location.origin + '/authentication/silent-callback', 

  scope: 'openid email',
  service_worker_relative_url:'/OidcServiceWorker.js',
  service_worker_only:true,

  extras :{
    acr_values: "pwd"
  }
};
guillaume-chervet commented 1 year ago

Thank you @raffaeler for all your information. Are you using service worker mode ?

raffaeler commented 1 year ago

I used the default, so yes

raffaeler commented 1 year ago

One quick question @guillaume-chervet If I disable the service worker by setting service_worker_only=false, is the state validation skipped or not? I am looking for a workaround while you get the time to fix this.

Thank you

guillaume-chervet commented 1 year ago

Hi @raffaeler, sorry for the delay. You can disable service worker by removing the line that reference the service worker in the configuration. You may also need to uninstall the service worker manually.

raffaeler commented 1 year ago

Thank you @guillaume-chervet. Do you believe that the state not valid error would go away? (can't test this now)

guillaume-chervet commented 1 year ago

hi @raffaeler ,

I have finally got the time to test your case.

I cannot reproduce your error. It seems to work with Identity Server. The only thing I see in your code is that you send a number in the extras instead of a string.

Does it work without service worker?

raffaeler commented 1 year ago

No but I can't test it right now because I am traveling for a conference out of my country. BTW

Thank you

RDG83 commented 1 year ago

We are experiencing the same error.

We're using version 6.16.14 Also using Keycloak.

Config:

client_id: 'app-name', redirect_uri: 'http://app-name/authentication/callback', silent_redirect_uri: 'http://app-name/authentication/silent-callback', authority: 'https://XXX/auth/realms/XXX', scope: 'openid roles', service_worker_relative_url:'/OidcServiceWorker.js', service_worker_only: true,

It works without serviceworker, but we would like it to work with one.

If you need additional information, let me know.

guillaume-chervet commented 1 year ago

Hi @RDG83 , thank you for your feedback. Did you configure the trusteddomains.js file?

RDG83 commented 1 year ago

Hello @guillaume-chervet ,

yes we set it to the wildcard but it did not help.

Something I forgot to mention is that we are currently running version 6.6.7 with success. However sometimes we run into this issue and upgrading seems to be the logical solution.

guillaume-chervet commented 1 year ago

Thank you for the information @RDG83 , i understand, it may be a regression.