knpuniversity / oauth2-client-bundle

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

Error 400: redirect_uri_mismatch in Google OAuth2 with Symfony and KnpOAuth2ClientBundle #451

Open MikeKe254 opened 3 months ago

MikeKe254 commented 3 months ago

I'm encountering a 400: redirect_uri_mismatch error when trying to authenticate with Google OAuth2 in a Symfony application using the KnpOAuth2ClientBundle. The redirect URI being sent from my application is:

http://localhost:8000/connect/google/check flowName=GeneralOAuthFlow

However, Google does not allow whitespace in the redirect URI. The correct format should be:

http://localhost:8000/connect/google/check?flowName=GeneralOAuthFlow

Despite having disabled additional parameters in my configuration, I'm still seeing them appended. My configuration is as follows:

# config/packages/knpu_oauth2_client.yaml
knpu_oauth2_client:
    clients:
        google_main:
            type: google
            client_id: '%env(OAUTH_GOOGLE_ID)%'
            client_secret: '%env(OAUTH_GOOGLE_SECRET)%'
            redirect_route: connect_google_check
            redirect_params: {}
  1. Verified that redirect_params is set to an empty array in the configuration.
  2. Checked for any hardcoded parameters or custom logic in my code but cant seem to find it
  3. Cleared browser cache and tried in incognito mode.
bocharsky-bw commented 3 months ago

Hey @MikeKe254 ,

Are you sure that flowName param isn't set somewhere in your app? I don't see knpuniversity/oauth2-client-bundle sets it somewhere in the code at all, am I missing something? Could you point me out?

MikeKe254 commented 3 months ago

Hi @bocharsky-bw,

I've reviewed my code thoroughly and couldn't find where the flowName parameter might be coming from. Below is the relevant code for my Google OAuth2 setup:

GoogleAuthenticator.php:

<?php
// src/Security/GoogleAuthenticator.php
namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

class GoogleAuthenticator extends OAuth2Authenticator implements AuthenticationEntryPointInterface
{
    private $clientRegistry;
    private $entityManager;
    private $router;

    public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $entityManager, RouterInterface $router)
    {
        $this->clientRegistry = $clientRegistry;
        $this->entityManager = $entityManager;
        $this->router = $router;
    }

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

    public function authenticate(Request $request): Passport
    {
        $client = $this->clientRegistry->getClient('google_main');
        $accessToken = $this->fetchAccessToken($client);

        return new SelfValidatingPassport(
            new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
                $googleUser = $client->fetchUserFromToken($accessToken);
                $email = $googleUser->getEmail();

                // Check if the user has already logged in with Google before
                //my logic here

                // Set the Google ID
                $user->setGoogleId($googleUser->getId());

                $this->entityManager->persist($user);
                $this->entityManager->flush();

                return $user;
            })
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return new RedirectResponse($this->router->generate('app_homepage'));
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new Response($exception->getMessageKey(), Response::HTTP_FORBIDDEN);
    }

    public function start(Request $request, AuthenticationException $authException = null): Response
    {
        return new RedirectResponse('/connect/google', Response::HTTP_TEMPORARY_REDIRECT);
    }
}

GoogleController.php:

<?php
// src/Controller/GoogleController.php
namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Controller\Bootstrap\DefaultLayoutController;

class GoogleController extends DefaultLayoutController
{
    #[Route('/connect/google', name: 'connect_google_start')]
    public function connectAction(ClientRegistry $clientRegistry)
    {
        return $clientRegistry
            ->getClient('google_main')
            ->redirect([
                'email', 'profile'
            ]);
    }

    #[Route('/connect/google/check', name: 'connect_google_check')]
    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
    {
        $client = $clientRegistry->getClient('google_main');

        try {
            $user = $client->fetchUser();
            var_dump($user); die;
        } catch (IdentityProviderException $e) {
            var_dump($e->getMessage()); die;
        }
    }
}

I've verified the configuration and checked for any potential sources of the flowName parameter. The issue persists, and I’m not seeing where this parameter might be added. Could you help me identify if there's something I might be missing?