knpuniversity / oauth2-client-bundle

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

Google and facebook log in not working with Symfony 5.4 #410

Closed vitoff34 closed 1 year ago

vitoff34 commented 1 year ago

Hello, I am trying to implement this bundle to use social logins for my app. I want to implement facebook and google. I follow the recommended steps but facebook gives me "Something went wrong. We are working on it," and google is getting stuck after the user selects a google account and nothing happens. With google I think the error happens in fetchAccessToken in authenticate method because I have tried dd() before it and it works. However after this method call the app is stuck in a forever loop Here is my code: GoogleController:

<?php

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class GoogleController extends AbstractController
{
    /**
     * Link to this controller to start the "connect" process
     *
     * @Route("/connect/google", name="connect_google_start")
     */
    public function connectAction(ClientRegistry $clientRegistry)
    {
        // will redirect to google!
        return $clientRegistry
            ->getClient('google') // key used in config/packages/knpu_oauth2_client.yaml
            ->redirect([
                'openid'
            ]);
    }

    /**
     * After going to google, you're redirected back here
     * because this is the "redirect_route" you configured
     * in config/packages/knpu_oauth2_client.yaml
     *
     * @Route("/connect/google/check", name="connect_google_check")
     */
    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
    {

        // leave this method blank and create a Guard authenticator
    }
}

`

GoogleAuthenticator: `<?php

namespace App\Security;

use App\Entity\User;

// your user entity use Doctrine\ORM\EntityManagerInterface; use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Provider\GoogleUser; 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 $clientRegistry; private EntityManagerInterface $entityManager; private RouterInterface $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
{
    // continue ONLY if the current ROUTE matches the check ROUTE
    return $request->attributes->get('_route') === 'connect_google_check';
}

public function authenticate(Request $request): Passport
{

    $client = $this->clientRegistry->getClient('google');
    $accessToken = $this->fetchAccessToken($client);

    return new SelfValidatingPassport(
        new UserBadge($accessToken->getToken(), function () use ($accessToken, $client)
        {
            /** @var GoogleUser $googleUser */
            $googleUser = $client->fetchUserFromToken($accessToken);

            $email = $googleUser->getEmail();

            // 1) have they logged in with Google before? Easy!
            $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['googleId' => $googleUser->getId()]);

            if ($existingUser)
            {
                return $existingUser;
            }

            // 2) do we have a matching user by email?
            $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);

            // 3) Maybe you just want to "register" them by creating
            // a User object
            $user->setGoogleId($googleUser->getId());
            $this->entityManager->persist($user);
            $this->entityManager->flush();

            return $user;
        })
    );
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
    // change "app_homepage" to some route in your app
    $targetUrl = $this->router->generate('homepage');

    return new RedirectResponse($targetUrl);

    // or, on success, let the request continue to be handled by the controller
    //return null;
}

public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
    $message = strtr($exception->getMessageKey(), $exception->getMessageData());

    return new Response($message, Response::HTTP_FORBIDDEN);
}

/**
 * Called when authentication is needed, but it's not sent.
 * This redirects to the 'login'.
 */
public function start(Request $request, AuthenticationException $authException = null): Response
{
    return new RedirectResponse(
        '/connect/', // might be the site, where users choose their oauth provider
        Response::HTTP_TEMPORARY_REDIRECT
    );
}

}`

FacebookController: `<?php

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route;

class FacebookController extends AbstractController { /**

FacebookAuthenticator: `<?php namespace App\Security;

use App\Entity\User; // your user entity 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 FacebookAuthenticator 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
{
    // continue ONLY if the current ROUTE matches the check ROUTE
    return $request->attributes->get('_route') === 'connect_facebook_check';
}

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

    return new SelfValidatingPassport(
        new UserBadge($accessToken->getToken(), function() use ($accessToken, $client) {
            /** @var FacebookUser $facebookUser */
            $facebookUser = $client->fetchUserFromToken($accessToken);

            $email = $facebookUser->getEmail();

            // 1) have they logged in with Facebook before? Easy!
            $existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['facebookId' => $facebookUser->getId()]);

            if ($existingUser) {
                return $existingUser;
            }

            // 2) do we have a matching user by email?
            $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);

            // 3) Maybe you just want to "register" them by creating
            // a User object
            $user->setFacebookId($facebookUser->getId());
            $this->entityManager->persist($user);
            $this->entityManager->flush();

            return $user;
        })
    );
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
    // change "app_homepage" to some route in your app
    $targetUrl = $this->router->generate('app_homepage');

    return new RedirectResponse($targetUrl);

    // or, on success, let the request continue to be handled by the controller
    //return null;
}

public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
    $message = strtr($exception->getMessageKey(), $exception->getMessageData());

    return new Response($message, Response::HTTP_FORBIDDEN);
}

/**
 * Called when authentication is needed, but it's not sent.
 * This redirects to the 'login'.
 */
public function start(Request $request, AuthenticationException $authException = null): Response
{
    return new RedirectResponse(
        '/connect/', // might be the site, where users choose their oauth provider
        Response::HTTP_TEMPORARY_REDIRECT
    );
}

}`

security.yaml: main: custom_authenticators:

knpu_oauth2_client.yaml: knpu_oauth2_client: clients:

configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration

    facebook_main:
        # this will be one of the supported types
        type: facebook
        client_id: '%env(OAUTH_FACEBOOK_ID)%'
        client_secret: '%env(FACEBOOK_SECRET)%'
        # the route that you're redirected to after
        # see the controller example below
        redirect_route: connect_facebook_check
        redirect_params: { }
        graph_api_version: v2.12
    google:
        # this will be one of the supported types
        type: google
        client_id: '%env(GOOGLE_CLIENT_ID)%'
        client_secret: '%env(GOOGLE_SECRET)%'
        # the route that you're redirected to after
        # see the controller example below
        redirect_route: connect_google_check
        redirect_params: { }

Any help will be much appreciated! Thank you