I followed this tutorial : https://github.com/knpuniversity/oauth2-client-bundle.
The versions I am using are Symfony 6.1.12 and PHP 8.1.0.
Currently, I log in with Google but not with Facebook. What did I miss ?
Thanks to var_dump($user);
in my FacebookController.php, I can see my Facebook data appearing. But I don’t connect with Facebook.
Because it works with Google, I am sharing also code from GoogleController.php and GoogleAuthenticator.php.
Thank you for your help.
My code in config/packages/knpu_oauth2_client.yaml :
knpu_oauth2_client:
clients:
facebook:
type: facebook
client_id: '%env(OAUTH_FACEBOOK_ID)%'
client_secret: '%env(OAUTH_FACEBOOK_SECRET)%'
redirect_route: connect_facebook_check
redirect_params: {}
graph_api_version: v2.12
In security.yaml :
security:
password_hashers:
SymfonyComponentSecurityCoreUserPasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: AppEntityUser
property: email
oauth:
id: knpu.oauth2.user_provider
enable_authenticator_manager: true
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
form_login:
login_path: login
check_path: login
logout:
path: logout
target: home
entry_point: AppSecurityAppCustomAuthenticator
custom_authenticators:
- AppSecurityAppCustomAuthenticator
- AppSecurityFacebookAuthenticator
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/connect, roles: PUBLIC_ACCESS }
I added variables in .env :
OAUTH_FACEBOOK_ID=fb_id
OAUTH_FACEBOOK_SECRET=fb_secret
My FacebookController :
<?php
namespace AppControllerFront;
use KnpUOAuth2ClientBundleClientClientRegistry;
use LeagueOAuth2ClientProviderExceptionIdentityProviderException;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentRoutingAnnotationRoute;
class FacebookController extends AbstractController
{
#[Route('/connect/facebook', name: 'connect_facebook')]
public function connectAction(ClientRegistry $clientRegistry)
{
return $clientRegistry
->getClient('facebook')
->redirect([
'public_profile', 'email'
], []);
}
#[Route('/connect/facebook/check', name: 'connect_facebook_check')]
public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
{
/** @var KnpUOAuth2ClientBundleClientProviderFacebookClient $client */
$client = $clientRegistry->getClient('facebook');
try {
/** @var LeagueOAuth2ClientProviderFacebookUser $user */
$user = $client->fetchUser();
var_dump($user); die;
} catch (IdentityProviderException $e) {
var_dump($e->getMessage()); die;
}
}
}
My GoogleController :
<?php
namespace AppControllerFront;
use KnpUOAuth2ClientBundleClientClientRegistry;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentRoutingAnnotationRoute;
class GoogleController extends AbstractController
{
#[Route('/connect/google', name: 'connect_google')]
public function connectAction(ClientRegistry $clientRegistry)
{
//Redirect to google
return $clientRegistry->getClient('google')->redirect([], []);
}
/**
* 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)
{
return $this->redirectToRoute('home');
// ** if you want to *authenticate* the user, then
// leave this method blank and create a Guard authenticator
}
}
My FacebookAuthenticator.php :
<?php
namespace AppSecurity;
use AppEntityUser;
use DoctrineORMEntityManagerInterface;
use KnpUOAuth2ClientBundleClientClientRegistry;
use KnpUOAuth2ClientBundleSecurityAuthenticatorOAuth2Authenticator;
use LeagueOAuth2ClientProviderFacebookUser;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingRouterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreExceptionAuthenticationException;
use SymfonyComponentSecurityHttpAuthenticatorPassportBadgeUserBadge;
use SymfonyComponentSecurityHttpAuthenticatorPassportPassport;
use SymfonyComponentSecurityHttpAuthenticatorPassportSelfValidatingPassport;
use SymfonyComponentSecurityHttpEntryPointAuthenticationEntryPointInterface;
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');
$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
{
$targetUrl = $this->router->generate('home');
return new RedirectResponse($targetUrl);
}
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
);
}
}
And my GoogleAuthenticator.php :
<?php
namespace AppSecurity;
use AppEntityUser;
use LeagueOAuth2ClientProviderGoogleUser;
use DoctrineORMEntityManagerInterface;
use KnpUOAuth2ClientBundleClientClientRegistry;
use KnpUOAuth2ClientBundleSecurityAuthenticatorOAuth2Authenticator;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingRouterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreExceptionAuthenticationException;
use SymfonyComponentSecurityHttpAuthenticatorPassportBadgeUserBadge;
use SymfonyComponentSecurityHttpAuthenticatorPassportPassport;
use SymfonyComponentSecurityHttpAuthenticatorPassportSelfValidatingPassport;
class GoogleAuthenticator extends OAuth2Authenticator
{
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();
// have they logged in with Google before? Easy!
$existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
//User doesnt exist, we create it !
if (!$existingUser) {
$existingUser = new User();
$existingUser->setEmail($email);
$existingUser->setGoogleId($googleUser->getId());
$existingUser->setHostedDomain($googleUser->getHostedDomain());
$this->entityManager->persist($existingUser);
}
$existingUser->setAvatar($googleUser->getAvatar());
$this->entityManager->flush();
return $existingUser;
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return new RedirectResponse(
$this->router->generate('home')
);
// 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/',
Response::HTTP_TEMPORARY_REDIRECT
);
}
}
2
Answers
Your connectCheckAction is not blank, it should be if you use Symfony Authenticator and delete these lines in your security file :
You can look at this website for help: https://bogomolov.tech/Facebook-Log-Id-Symfony/#:~:text=Workflow%20overview%20is%3A%201%20User%20clicks%20the%20Facebook,auth%20data%20and%20logs%20in%20with%20Symfony%20handlers.