knpuniversity / oauth2-client-bundle

Easily talk to an OAuth2 server for social functionality in Symfony
https://symfonycasts.com
MIT License
775 stars 145 forks source link

Invalid state passed in parameters callback url #347

Open nitrique opened 2 years ago

nitrique commented 2 years ago

Hi,

Using this module with Keycloak client, I randomly get the error "Invalid state passed in parameters callback url", in both Firefox and Brave (chromium).

Using Safari with localhost (without https), this error is shown every time and I cannot access to my interface. I've put some breakpoint in this bundle, and found out that the cookie is cleared between navigations.

It seems that Safari try to protect navigation and tracking when using redirect from another site, which is indeed problematic when using oauth2. Note that the cookie is set in "lax" mode. But in reality it clear the "phpsessid" cookie :( .

Do some of you guys already had this matter and found out a solution ?

Thanks, Best regards.

Nicolas

tacman commented 2 years ago

I'm running into this same issue, using Symfony 5.4 and the new authentication manager.

I'm guessing it has something to do with the recent changes to session management, but not sure. Open to suggestions.

tacman commented 2 years ago

In the end, the issue was an invalid secret. The 404 threw me off.

kevincerro commented 2 years ago

I'm facing the same issue with Google login.

Symfony 5.4 with new authenticator manager

Session key "knpu.oauth2_client_state" is not found, so "expectedState" is null. This condition is resolved to "true" and exception is triggered. https://github.com/knpuniversity/oauth2-client-bundle/blob/e7e0cc5d5aa3b04210c5c56ada69a7a8dc858abc/src/Client/OAuth2Client.php#L98-L100

HysteriaKa commented 2 years ago

Hey, maube it's too late, but i had that error too, because i went one page back, instead of the origin call. (hope you understand what i mean)

nitrique commented 2 years ago

Not too late, problem is not solved and this package looks to be not actively maintained. We have planed to implement it by ourself.

bocharsky-bw commented 2 years ago

Could anyone create a PR that would fix this issue? I'd be happy to review it

lu43n commented 2 years ago

I have the same problem with Safari 15.1. The session with the state key is set in the redirect () method in /Client/OAuth2Client.php, while after redirecting in the getAccessToken method, the session with the key self :: OAUTH2_SESSION_STATE_KEY is missing.

It's weird that everything works fine in Chrome. Anyone can help?

ikerib commented 1 year ago

One more with the same issue I created my server following this https://davegebler.com/post/php/build-oauth2-server-php-symfony#what-we'll-be-building and then another app with this bundle. The user is logged in on the oauth server but when it comes back and try to find this session variable is null

ikerib commented 1 year ago

I'm facing the same issue with Google login.

Symfony 5.4 with new authenticator manager

Session key "knpu.oauth2_client_state" is not found, so "expectedState" is null. This condition is resolved to "true" and exception is triggered.

https://github.com/knpuniversity/oauth2-client-bundle/blob/e7e0cc5d5aa3b04210c5c56ada69a7a8dc858abc/src/Client/OAuth2Client.php#L98-L100

did you found a solution?

HysteriaKa commented 1 year ago

hey, sorry for my late answer ! I don t really remind all i did, but i had to follow a tutoriel for Symfony 6, there has been somes changes, even i run a symfony 5.4. I remember i had to change some specific stuff in the bundle, the auth thing is not the same anymore. It was that tutoriel https://www.dev-web.io/2022/03/07/symfony-6-sauthentifier-avec-google-facebook-github/ and i think it's the extends i had to change but not only in my controller image I really hope, it helps

GARINED commented 1 year ago

Hi,

the problem is still not solved... I already tried to do this in my construct but it still doesn't work

dpi commented 1 year ago

For me the error

Invalid state parameter passed in callback URL.

Was simply due to having multiple PHP servers (via Kubernetes) serving users. If you got unlucky (highly likely) then you get a different server with different sessions.

From the user perspective the browser will show the browser received a cookie for a brief subsecond, then it is immediately purged.

Switching to an alternative session management strategy worked. Notably from the linked page, this line is particularly relevant:

Symfony stores sessions in files by default. If your application is served by multiple servers, you'll need to use a database instead to make sessions work across different servers.

gl

cyraid commented 1 year ago

I had invalid state pop up recently, but that's because I had changed my cookie_samesite from lax to strict.. But apparently Google login doesn't like strict, so I changed it back to lax.

ManoloTonto1 commented 3 months ago

Ok I solved this, here is what I had:

    public function supports(Request $request): ?bool {
        return $request->attributes->get("_route") === "oauth_connect";
    }

and it should be


    public function supports(Request $request): ?bool {
        return $request->attributes->get("_route") === "oauth_callback";
    }

You should start authenticating once the user reaches back to your page, not while sending them to the OAuth provider's page.

The reason no state is set is because you really don't have a state. This is also documented somewhere in the middle of the README.

Hope this helps!!!

zerowebcorp commented 3 months ago

Ok I solved this, here is what I had:

  public function supports(Request $request): ?bool {
      return $request->attributes->get("_route") === "oauth_connect";
  }

and it should be

  public function supports(Request $request): ?bool {
      return $request->attributes->get("_route") === "oauth_callback";
  }

You should start authenticating once the user reaches back to your page, not while sending them to the OAuth provider's page.

The reason no state is set is because you really don't have a state. This is also documented somewhere in the middle of the README.

Hope this helps!!!

Can you help me understand this better?

This is what I've

 public function supports(Request $request): ?bool
    {
        return $request->attributes->get('_route') === 'connect_google_check';
    }

image

I am facing this issue where a lot of users cannot login and getting Invalid State error. Mostly on iphone/mac

ManoloTonto1 commented 3 months ago

Ok I solved this, here is what I had:

    public function supports(Request $request): ?bool {
        return $request->attributes->get("_route") === "oauth_connect";
    }

and it should be

    public function supports(Request $request): ?bool {
        return $request->attributes->get("_route") === "oauth_callback";
    }

You should start authenticating once the user reaches back to your page, not while sending them to the OAuth provider's page. The reason no state is set is because you really don't have a state. This is also documented somewhere in the middle of the README. Hope this helps!!!

Can you help me understand this better?

This is what I've

 public function supports(Request $request): ?bool
    {
        return $request->attributes->get('_route') === 'connect_google_check';
    }

image

I am facing this issue where a lot of users cannot login and getting Invalid State error. Mostly on iphone/mac

Ok, to understand why you are getting the error you need to understand OAUTH in general and also how symfony handles authentication using authenticators. Disclaimer, I am using Symfony 5.4.

Simplified oauth flow

image

As you can see our server has 2 points of interaction,

  1. handling the initialization of the flow /oauth/connect/{provider}
  2. having a callback once the provider is done processing the user's log in request /oauth/connect/{provider}/callback

Now, here is where things get weird with symfony and its authenticators: The callback route should be intercepted by the authenticator (think of it as some kind of middleware) to handle the login logic.

Init

    /**
     * Link to this controller to start the "connect" process
     *
     * @Route("/oauth/connect/{provider}", name="oauth_connect")
     */
    public function startOauthFlow(
        Request $request,
        ClientRegistry $clientRegistry
    ) {
        $provider = $request->get("provider");
        try {
            return $clientRegistry
                ->getClient($provider)
                ->redirect(["openid", "public_profile", "email"], []);
        } catch (Exception $e) {
            $this->addFlash("danger", self::START_ERROR . $e->getMessage());
            return $this->redirectToRoute("app_login");
        }
    }

Callback

    /**
     * After going to the provider, you're redirected back here
     * because this is the "redirect_route" you configured
     * in config/packages/knpu_oauth2_client.yaml
     *
     * @Route("/oauth/connect/{provider}/callback", name="oauth_callback", schemes={"https"})
     */
    public function callback(Request $request, ClientRegistry $clientRegistry) {
        // this can be empty, it will get intercepted by the authenticator
        return;
    }

The callback gets intercepted by this function in your authenticator:

    public function authenticate(Request $request): PassportInterface {
        $provider = $request->get("provider");
        $client = $this->clientRegistry->getClient($provider);
        $accessToken = $this->fetchAccessToken($client);
        $handler = new OauthAuthentication(
            $this->clientRegistry,
            $this->entityManager,
            $accessToken
        );
        return $handler->Authenticate($request);
    }

after that the onSuccess Logic

The last thing you need for this to work out is to add your authenticator to your main firewall and it should work.

firewalls:
       main:
             custom_authenticator: App\Security\Authenticators\OAuthAuthenticator 

as for support, it's only for symfony to know which authenticator it will use. so if you just keep you oauth_callback route you should be fine.

    public function supports(Request $request): ?bool {
        return $request->attributes->get("_route") == "oauth_callback";
    }

I hope this helps out!

ikerib commented 3 months ago

in my case, the problem was I pressed the back button on the browser

zerowebcorp commented 3 months ago

Ok I solved this, here is what I had:

  public function supports(Request $request): ?bool {
      return $request->attributes->get("_route") === "oauth_connect";
  }

and it should be

  public function supports(Request $request): ?bool {
      return $request->attributes->get("_route") === "oauth_callback";
  }

You should start authenticating once the user reaches back to your page, not while sending them to the OAuth provider's page. The reason no state is set is because you really don't have a state. This is also documented somewhere in the middle of the README. Hope this helps!!!

Can you help me understand this better? This is what I've

 public function supports(Request $request): ?bool
    {
        return $request->attributes->get('_route') === 'connect_google_check';
    }

image I am facing this issue where a lot of users cannot login and getting Invalid State error. Mostly on iphone/mac

Ok, to understand why you are getting the error you need to understand OAUTH in general and also how symfony handles authentication using authenticators. Disclaimer, I am using Symfony 5.4.

Simplified oauth flow

image

As you can see our server has 2 points of interaction,

  1. handling the initialization of the flow /oauth/connect/{provider}
  2. having a callback once the provider is done processing the user's log in request /oauth/connect/{provider}/callback

Now, here is where things get weird with symfony and its authenticators: The callback route should be intercepted by the authenticator (think of it as some kind of middleware) to handle the login logic.

Init

  /**
   * Link to this controller to start the "connect" process
   *
   * @Route("/oauth/connect/{provider}", name="oauth_connect")
   */
  public function startOauthFlow(
      Request $request,
      ClientRegistry $clientRegistry
  ) {
      $provider = $request->get("provider");
      try {
          return $clientRegistry
              ->getClient($provider)
              ->redirect(["openid", "public_profile", "email"], []);
      } catch (Exception $e) {
          $this->addFlash("danger", self::START_ERROR . $e->getMessage());
          return $this->redirectToRoute("app_login");
      }
  }

Callback

  /**
   * After going to the provider, you're redirected back here
   * because this is the "redirect_route" you configured
   * in config/packages/knpu_oauth2_client.yaml
   *
   * @Route("/oauth/connect/{provider}/callback", name="oauth_callback", schemes={"https"})
   */
  public function callback(Request $request, ClientRegistry $clientRegistry) {
      // this can be empty, it will get intercepted by the authenticator
      return;
  }

The callback gets intercepted by this function in your authenticator:

  public function authenticate(Request $request): PassportInterface {
      $provider = $request->get("provider");
      $client = $this->clientRegistry->getClient($provider);
      $accessToken = $this->fetchAccessToken($client);
      $handler = new OauthAuthentication(
          $this->clientRegistry,
          $this->entityManager,
          $accessToken
      );
      return $handler->Authenticate($request);
  }

after that the onSuccess Logic

The last thing you need for this to work out is to add your authenticator to your main firewall and it should work.

firewalls:
       main:
             custom_authenticator: App\Security\Authenticators\OAuthAuthenticator 

as for support, it's only for symfony to know which authenticator it will use. so if you just keep you oauth_callback route you should be fine.

  public function supports(Request $request): ?bool {
      return $request->attributes->get("_route") == "oauth_callback";
  }

I hope this helps out!

Hi Thanks for the explanation. I believe I have the proper configuration. I am using Symfony 7.1 and this often gets Invalid State error. I even tried to disable the state option in the configuration.

My clients were frustrated. I finally resolved this yesterday by switching to HWIOAuthBundle and I no longer get the issue. This is clearly a bug in the knpuniversity eco system, unfortunately I don't know how to troubleshoot this. I posted my configuration in the thephpleague repo for the google auth, but it wasn't helpful as well. https://github.com/thephpleague/oauth2-google/issues/124

tacman commented 3 months ago

Thanks for posting this. I also believe that https://github.com/knpuniversity/oauth2-client-bundle/issues/436 is a bug in the library, but it's related to something deep in the cache or nginx configuration or something related to http v https. While I don't think my issue is related to yours, I guess I should consider your solution (switching to HWIOAuthBundle).