Skip to content

Instantly share code, notes, and snippets.

@alister
Forked from rwitchell/FacebookConnectController.php
Last active October 19, 2017 15:05
Show Gist options
  • Save alister/2f01d181924c612d5f31cfae8c450d4c to your computer and use it in GitHub Desktop.
Save alister/2f01d181924c612d5f31cfae8c450d4c to your computer and use it in GitHub Desktop.
oauth2-client-bundle install example
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
services:
comp_app.security.form_login_authenticator:
class: Comp\AppBundle\Security\FormLoginAuthenticator
arguments: ["@service_container"]
comp_app.security.my_facebook_authenticator:
class: Comp\AppBundle\Security\MyFacebookAuthenticator
#arguments: [ @service_container ]
facebook.handler:
class: Comp\AppBundle\Handler\FacebookHandler
arguments: ["%facebook_app_id%", "%facebook_app_secret%"]
autowire: true
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
form_themes: ['bootstrap_3_horizontal_layout.html.twig']
globals:
facebook_app_id: "%facebook_app_id%"
facebook: "@facebook.handler"
# Doctrine Extension Configuration
stof_doctrine_extensions:
orm:
default:
timestampable: true
sluggable: true
# FOS User Provider Bundle
fos_user:
db_driver: orm
firewall_name: main
user_class: Comp\AppBundle\Entity\User
service:
mailer: fos_user.mailer.twig_swift
registration:
confirmation:
enabled: true
registration:
confirmation:
template: email/registration_confirmation.email.twig
from_email:
address:
sender_name: Project X
resetting:
email:
template: email/password_resetting.email.twig
from_email:
address:
sender_name: Project X
knpu_oauth2_client:
clients:
# will create service: "knpu.oauth2.client.facebook"
# an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient
facebook:
type: facebook
client_id: "%facebook_app_id%"
client_secret: "%facebook_app_secret%"
redirect_route: facebook_token_response
redirect_params: {}
graph_api_version: v2.5
<?php
namespace Comp\AppBundle\Controller;
use Comp\AppBundle\Entity\User;
use Comp\AppBundle\Form\FacebookRegistrationType;
use League\OAuth2\Client\Provider\FacebookUser;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* Class FacebookConnectController
*/
class FacebookConnectController extends Controller
{
/**
* @Route("/connect/facebook", name="connect_facebook")
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function connectFacebookAction(Request $request)
{
// redirect to Facebook
$facebookClient = $this->get('knpu.oauth2.registry')
// key used in config.yml
->getClient('facebook');
return $facebookClient->redirect(
[
'public_profile',
'email',
//'user_friends',
]
);
}
/**
* @Route("/connect/facebook-check", name="connect_facebook_check")
*/
public function connectFacebookActionCheck()
{
// this function won't be reached.
$handler = $this->get('facebook.handler');
$handler->getLongLivedAccessToken($handler->getAccessToken());
}
/**
* @Route("/connect/facebook/registration", name="connect_facebook_registration")
* @param Request $request
*
* @return null|\Symfony\Component\HttpFoundation\Response
*/
public function finishRegistration(Request $request)
{
/** @var FacebookUser $facebookUser */
$facebookUser = $this->get('comp_app.security.my_facebook_authenticator')
->getUserInfoFromSession($request);
if (!$facebookUser) {
throw $this->createNotFoundException('How did you get here without user information!?');
}
$user = new User();
$user->setFacebookId($facebookUser->getId());
$user->setEmail($facebookUser->getEmail());
$form = $this->createForm(new FacebookRegistrationType(), $user);
$form->handleRequest($request);
if ($form->isValid()) {
// encode the password manually
$plainPassword = $form['plainPassword']->getData();
$encodedPassword = $this->get('security.password_encoder')
->encodePassword($user, $plainPassword);
$user->setPassword($encodedPassword);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
// remove the session information
$request->getSession()->remove('facebook_user');
// log the user in manually
$guardHandler = $this->container->get('security.authentication.guard_handler');
return $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$this->container->get('comp_app.security.my_facebook_authenticator'),
'main' // the firewall key
);
}
return $this->render(
'AppBundle:Security:registration_facebook.html.twig',
[
'form' => $form->createView(),
]
);
}
}
<?php
namespace Comp\AppBundle\Controller;
use Exception;
use Facebook\Exceptions\FacebookSDKException;
use Comp\AppBundle\Entity\User;
use Comp\AppBundle\Handler\FacebookHandler;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Class FacebookController
*/
class FacebookController extends Controller
{
/**
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*
* @Route("/facebook-get-permission", name="facebook_get_permission" )
*/
public function getPermissionAction(Request $request)
{
/** @var FacebookHandler $handler */
$handler = $this->get('facebook.handler');
$redirectUrl = $this->generateUrl('facebook_token_response', [], UrlGeneratorInterface::ABSOLUTE_URL);
$handler->setPermission('email')
->setPermission('user_likes')
->setPermission('user_friends')
->setPermission('public_profile');
$loginUrl = $handler->getLoginUrl($redirectUrl, $handler->getPermissions());
return $this->redirect($loginUrl);
}
/**
* @return array
* @throws FacebookSDKException
*
* @Route("/facebook-find-friends", name="facebook_show_friends")
* @Method("GET")
* @Template("AppBundle:Facebook:show_friends.html.twig")
*/
public function showFriendsAction()
{
return $this->facebookRequest('/me/friends');
}
/**
* @param string $endpoint
*
* @return \Facebook\FacebookResponse|null|\Symfony\Component\HttpFoundation\RedirectResponse
* @throws FacebookSDKException
*/
public function facebookRequest($endpoint)
{
$response = null;
/** @var FacebookHandler $handler */
$handler = $this->get('facebook.handler');
/** @var User $user */
$user = $this->getUser();
if (!$handler->hasToken($user)) {
return $this->redirectToRoute("facebook_get_permission");
}
try {
$response = $handler->SocialNetworkRequest($endpoint);
} catch (FacebookSDKException $e) {
if ($e->getMessage() === "You must provide an access token.") {
return $this->redirectToRoute('facebook_get_permission');
}
if ($e->getMessage() === "Access token not in user.") {
return $this->redirectToRoute('facebook_get_permission');
}
return $this->redirectToRoute('facebook_get_permission');
//throw new FacebookSDKException($e->getMessage());
} catch (Exception $e) {
echo $e;
}
return [
'facebook' => $response,
];
}
/**
* @param Request $request
*
* @Route("/facebook-token-response", name="facebook_token_response")
* @Method("GET")
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function tokenResponseAction(Request $request)
{
/** @var FacebookHandler $handler */
$handler = $this->get('facebook.handler');
$accessToken = $handler->getAccessToken();
if (isset($accessToken)) {
$accessToken = $handler->getLongLivedAccessToken($accessToken);
$user = $this->getUser();
$user->setFacebookAccessToken($accessToken);
$userRepo = $this->getDoctrine()->getRepository("AppBundle:User");
$userRepo->save($user);
}
//return $this->redirectToRoute($request->get('redirectTo'))
return $this->redirectToRoute('facebook_show_friends');
}
}
<?php
namespace Comp\AppBundle\Handler;
use Facebook\Exceptions\FacebookSDKException;
use Facebook\Facebook;
use Comp\AppBundle\Entity\User;
/**
* Class FacebookHandler
*/
class FacebookHandler extends SocialHandler
{
/**
* used by friend controller to take URL parameter and
* assign correct column in User table.
*/
const ENTITY_PROPERTY = 'facebookId';
/**
* @var Facebook
*/
protected $socialNetwork;
/**
* FacebookHandler constructor.
*
* @param string $facebookAppId
* @param string $facebookAppSecret
*/
public function __construct($facebookAppId, $facebookAppSecret)
{
parent::__construct();
$this->socialNetwork = new Facebook(
[
'app_id' => $facebookAppId,
'app_secret' => $facebookAppSecret,
'default_graph_version' => 'v2.5',
]
);
return $this;
}
/**
* @return Facebook
*/
public function getSocialNetwork()
{
return $this->socialNetwork;
}
/**
* @param string|null $redirectUrl
*
* @return \Facebook\Authentication\AccessToken|null
*/
public function getAccessToken($redirectUrl = null)
{
return $this->socialNetwork->getRedirectLoginHelper()->getAccessToken($redirectUrl);
}
/**
* @param string $accessToken
*
* @return \Facebook\Authentication\AccessToken
*/
public function getLongLivedAccessToken($accessToken)
{
return $this->socialNetwork->getOAuth2Client()->getLongLivedAccessToken($accessToken);
}
/**
* @param string $redirectUrl
* @param array $scope
* @param string $separator
*
* @return string
*/
public function getLoginUrl($redirectUrl, array $scope = [], $separator = '&')
{
return $this->socialNetwork->getRedirectLoginHelper()->getLoginUrl($redirectUrl, $scope, $separator);
}
/**
* @param User $user
*
* @return \Facebook\GraphNodes\GraphNode|void
*/
public function getProfilePhoto($user)
{
return $this->SocialNetworkRequest('/'.$user->getFacebookId().'/picture', $user);
return $request->getGraphNode();
}
/**
* @param string $endpoint
* @param User|null $user
*
* @return \Facebook\FacebookResponse
*
*/
public function SocialNetworkRequest($endpoint, $user = null)
{
if (!$this->hasToken($user)) {
return false;
}
$response = $this->socialNetwork->get($endpoint);
return $response;
}
/**
* @param User|null $user
*
* @return bool
*/
public function hasToken(User $user = null)
{
// check if default is set
if (!empty($this->socialNetwork->getDefaultAccessToken())) {
return true;
}
if (empty($user)) {
return false;
}
// check if we have token on user entity
$userToken = $user->getFacebookAccessToken() ?: null;
if (empty($userToken)) {
return false;
}
$this->socialNetwork->setDefaultAccessToken($userToken);
return true;
}
}
<?php
namespace Comp\AppBundle\Security;
use Doctrine\ORM\EntityManager;
use Comp\AppBundle\Entity\User;
use Comp\AppBundle\Handler\FacebookHandler;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Security\Exception\FinishRegistrationException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\FacebookUser;
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\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
/**
* Class MyFacebookAuthenticator
*/
class MyFacebookAuthenticator extends SocialAuthenticator
{
/**
* @var FacebookClient
*/
private $clientRegistry;
/**
* @var EntityManager
*/
private $em;
/**
* @var RouterInterface
*/
private $router;
/**
* MyFacebookAuthenticator constructor.
*
* @param ClientRegistry $clientRegistry
* @param EntityManager $em
* @param RouterInterface $router
*
*/
public function __construct(ClientRegistry $clientRegistry, EntityManager $em, RouterInterface $router)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
$this->router = $router;
}
/**
* Returns a response that directs the user to authenticate.
*
* This is called when an anonymous request accesses a resource that
* requires authentication. The job of this method is to return some
* response that "helps" the user start into the authentication process.
*
* Examples:
* A) For a form login, you might redirect to the login page
* return new RedirectResponse('/login');
* B) For an API token authentication system, you return a 401 response
* return new Response('Auth header required', 401);
*
* @param Request $request The request that resulted in an AuthenticationException
* @param AuthenticationException $authException The exception that started the authentication process
*
* @return Response
*/
public function start(Request $request, AuthenticationException $authException = null)
{
// this is never called, as all entries are redirected to form_login
}
/**
* Get the authentication credentials from the request and return them
* as any type (e.g. an associate array). If you return null, authentication
* will be skipped.
*
* Whatever value you return here will be passed to getUser() and checkCredentials()
*
* For example, for a form login, you might:
*
* return array(
* 'username' => $request->request->get('_username'),
* 'password' => $request->request->get('_password'),
* );
*
* Or for an API token that's on a header, you might use:
*
* return array('api_key' => $request->headers->get('X-API-TOKEN'));
*
* @param Request $request
*
* @return mixed|null
* @throws IdentityProviderException
*/
public function getCredentials(Request $request)
{
// Pre 3.4, getCredentials() returns null to skip the authenticator
// In 3.4 this will have already been called, and so will succeed here
if (!$this->supports($request)) {
return;
}
try {
return $this->fetchAccessToken($this->getFacebookClient());
} catch (IdentityProviderException $e) {
throw $e;
}
}
/**
* Symfony 3.4+ splits getCredentials() and calls supports() first.
*
* Create (and use in getCredentials) to be ready and 3.x and 3.4+
* compatible.
*
* @param Request $request
*
* @return bool
*/
public function supports(Request $request)
{
return ($request->getPathInfo() === '/connect/facebook-check');
}
/**
* @return \KnpU\OAuth2ClientBundle\Client\OAuth2Client
*/
private function getFacebookClient()
{
return $this->clientRegistry->getClient('facebook');
}
/**
* Return a UserInterface object based on the credentials.
*
* The *credentials* are the return value from getCredentials()
*
* You may throw an AuthenticationException if you wish. If you return
* null, then a UsernameNotFoundException is thrown for you.
*
* @param mixed $credentials
* @param UserProviderInterface $userProvider
*
* @throws AuthenticationException
*
* @return UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
/** @var FacebookUser $facebookUser */
$facebookUser = $this->getFacebookClient()->fetchUserFromToken($credentials);
$userRepo = $this->em->getRepository('AppBundle:User');
// 1) Have they logged in with Facebook before?
/** @var User $existingUser */
$existingUser = $userRepo->findOneBy([FacebookHandler::ENTITY_PROPERTY => $facebookUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// 2) Do we have a matching user by email?
$email = $facebookUser->getEmail();
$user = $userRepo->findOneBy(['email' => $email]);
if (!$user) {
// throw a special exception we created - see onAuthenticaitonFailure
throw new FinishRegistrationException($facebookUser);
}
// Take $user (which can be null if none found above, or have the user without facebook ID)
// and attach the facebook ID to the account.
$user->setFacebookId($facebookUser->getId());
//TODO: $user->setFacebookAccessToken($this->facebookClient->getAccessToken());
$userRepo->save($user);
return $user;
}
/**
* Called when authentication executed, but failed (e.g. wrong username password).
*
* This should return the Response sent back to the user, like a
* RedirectResponse to the login page or a 403 response.
*
* If you return null, the request will continue, but the user will
* not be authenticated. This is probably not what you want to do.
*
* @param Request $request
* @param AuthenticationException $exception
*
* @return Response|null
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($exception instanceof FinishRegistrationException) {
$this->saveUserInfoToSession($request, $exception);
$registrationUrl = $this->router->generate('connect_facebook_registration');
return new RedirectResponse($registrationUrl);
}
$this->saveAuthenticationErrorToSession($request, $exception);
$loginUrl = $this->router->generate('fos_user_security_login');
return new RedirectResponse($loginUrl);
}
/**
* Called when authentication executed and was successful!
*
* This should return the Response sent back to the user, like a
* RedirectResponse to the last page they visited.
*
* If you return null, the current request will continue, and the user
* will be authenticated. This makes sense, for example, with an API.
*
* @param Request $request
* @param TokenInterface $token
* @param string $providerKey The provider (i.e. firewall) key
*
* @return Response|null
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if (!$url = $this->getPreviousUrl($request, $providerKey)) {
$url = $this->router->generate('comp_app_welcome_myhome');
}
// /** @var User $user */
// $user = $request->getUser();
// $user->setFacebookAccessToken($this->clientRegistry->getAccessToken());
return new RedirectResponse($url);
}
}
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
comp\AppBundle\Entity\User:
algorithm: bcrypt
cost: 4
role_hierarchy:
ROLE_ADMIN: ROSE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt|error)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
login_path: /login
logout: true
anonymous: true
guard:
authenticators:
- comp_app.security.form_login_authenticator
- comp_app.security.my_facebook_authenticator
# by default, use the start() function from FormLoginAuthenticator
entry_point: comp_app.security.form_login_authenticator
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/connect/facebook, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/connect/facebook-check, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/connect/facebook/registration, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetpw, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/logout, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/welcome, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: [ROLE_USER, ROLE_ADMIN] }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment