Skip to content

Instantly share code, notes, and snippets.

@rwitchell
Created May 23, 2016 14:28
Show Gist options
  • Save rwitchell/f380be61d6d738c78ab567e79b04ffa7 to your computer and use it in GitHub Desktop.
Save rwitchell/f380be61d6d738c78ab567e79b04ffa7 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)
{
if ($request->getPathInfo() != '/connect/facebook-check') {
// don't authenticate
return null;
}
try {
return $this->fetchAccessToken($this->getFacebookClient());
} catch (IdentityProviderException $e) {
throw $e;
}
}
/**
* @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] }
@alister
Copy link

alister commented Oct 19, 2017

Big thumbs up for this - I've used it as a base to get my own Oauth login/signup Guard written.

In my fork, I've just added some support for Symfony 3.4, where getCredentials() cannot return null, but instead first calls support(). I'm not yet sure if the interface change to require supports() will need any additional work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment