Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
MikeKe254 opened this issue Aug 18, 2024 · 2 comments

Comments

@MikeKe254
Copy link

MikeKe254 commented Aug 18, 2024

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
Copy link
Member

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
Copy link
Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants